Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (49.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
                snf.ui.main.show_vm_details(storage.vms.get(this.vm_id));
75
            }, this));
76

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

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

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

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

    
118

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
217
        update_images: function(images) {
218
            this.images = images;
219
            this.images_ids = _.map(this.images, function(img){return img.id});
220
            return this.images;
221
        },
222

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

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

    
243
        reset_categories: function() {
244
            var categories = this.get_categories(this.images);
245
            this.categories_list.find("li").remove();
246

    
247
            _.each(categories, _.bind(function(cat) {
248
                var el = $("<li />");
249
                el.text(cat);
250
                this.categories_list.append(el);
251
            }, this));
252

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

    
273
        hide_loading_view: function(images) {
274
            this.$(".images-list-cont .loading").hide();
275
            this.$(".images-list-cont .images-list").show();
276
            this.reset_categories();
277
            this.update_images(images);
278
            this.reset_images();
279
            this.select_image(this.selected_image);
280
            this.hide_list_loading();
281
        },
282

    
283
        select_type: function(type) {
284
            this.selected_type = type;
285
            this.types.removeClass("selected");
286
            this.types.filter("#type-select-" + this.selected_type).addClass("selected");
287
            this.images_storage.update_images_for_type(
288
                this.selected_type, 
289
                _.bind(this.show_loading_view, this), 
290
                _.bind(this.hide_loading_view, this)
291
            );
292
            this.update_layout_for_type(type);
293
        },
294

    
295
        update_layout_for_type: function(type) {
296
            if (type != "system") {
297
                this.$(".custom-action").hide();
298
            } else {
299
                this.$(".custom-action").hide();
300
            }
301

    
302
        },
303

    
304
        show_list_loading: function() {
305
            this.$(".images-list-cont").addClass("loading");
306
        },
307

    
308
        hide_list_loading: function() {
309
            this.$(".images-list-cont").removeClass("loading");
310
        },
311

    
312
        select_image: function(image) {
313
            console.log(image ? image.get('id'): null);
314
            if (image && image.get('id') && !_.include(this.images_ids, image.get('id'))) {
315
                image = undefined;
316
            }
317
            if (!image && this.images_ids.length) {
318
                if (this.selected_image && this.images_ids.indexOf(this.selected_image.id) > -1) {
319
                    image = this.selected_image;
320
                } else {
321
                    image = this.images_storage.get(this.images_ids[0]);
322
                }
323
            }
324
             
325
            // no images select null image so that next button gets hidden
326
            if (!this.images_ids.length) { image = undefined };
327
            
328
            if ((!this.selected_image && image) || (this.selected_image != image))
329
                this.trigger("change", image);
330

    
331
            this.selected_image = image;
332
                
333
            if (image) {
334
                this.images_list.find(".image-details").removeClass("selected");
335
                this.images_list.find(".image-details#create-vm-image-" + this.selected_image.id).addClass("selected");
336
                this.update_image_details(image);
337

    
338
            } else {
339
            }
340

    
341
            this.image_details.hide();
342
            this.validate();
343
        },
344

    
345
        update_image_details: function(image) {
346
            this.image_details_desc.hide().parent().hide();
347
            if (image.escape("description")) {
348
                this.image_details_desc.text(image.get("description")).show().parent().show();
349
            }
350
            var img = snf.ui.helpers.os_icon_tag(image.escape("OS"))
351
            if (image.get("name")) {
352
                this.image_details_title.html(img + image.escape("name")).show().parent().show();
353
            }
354
            
355
            var extra_details = this.image_details.find(".extra-details");
356
            // clean prevously added extra details
357
            extra_details.find(".image-detail").remove();
358

    
359
            var meta_keys = ['owner', 'OS', 'kernel', 'GUI'];
360
            var detail_tpl = ('<div class="clearfix image-detail {2}">' +
361
                             '<span class="title">{0}</span>' +
362
                             '<p class="value">{1}</p>' + 
363
                             '</div>');
364
            meta_keys = _.union(meta_keys, this.images_storage.display_metadata || []);
365

    
366
            _.each(meta_keys, function(key) {
367
                var value;
368
                var method = 'get_' + key.toLowerCase();
369
                var display_method = 'display_' + key.toLowerCase();
370
                 
371
                if (image[display_method]) {
372
                    value = image[display_method]();
373
                } else if (image[method]) {
374
                    value = image[method]();
375
                } else {
376
                    value = image.get(key);
377

    
378
                    if (!value) {
379
                        value = image.get_meta(key);
380
                    }
381
                }
382

    
383
                if (!value) { return; }
384
                
385
                var label = _(key.replace(/_/g," ")).capitalize();
386
                extra_details.append(detail_tpl.format(_.escape(label), _.escape(value), key.toLowerCase()));
387
            })
388
        },
389

    
390
        reset_images: function() {
391
            this.images_list.find("li").remove();
392
            _.each(this.images, _.bind(function(img){
393
                this.add_image(img);
394
            }, this))
395
            
396
            if (this.images.length) {
397
                this.images_list.parent().find(".empty").hide();
398
                this.images_list.show();
399
            } else {
400
                this.images_list.parent().find(".empty").show();
401
                this.images_list.hide();
402
            }
403

    
404
            var self = this;
405
            this.images_list.find(".image-details").click(function(){
406
                self.select_image($(this).data("image"));
407
            });
408
            
409
        },
410

    
411
        show: function() {
412
            // reset previous selection and state of the view
413
            this.selected_image = false;
414
            this.image_details.hide();
415
            this.parent.$(".create-controls").show();
416

    
417
            views.CreateImageSelectView.__super__.show.apply(this, arguments);
418
        },
419

    
420
        add_image: function(img) {
421
            var image = $(('<li id="create-vm-image-{1}"' +
422
                           'class="image-details clearfix">{2}{0}'+
423
                           '<span class="show-details">details</span>'+
424
                           '<span class="size"><span class="prepend">by </span>{5}</span>' + 
425
                           '<span class="owner">' +
426
                           '<span class="prepend"></span>' +
427
                           '{3}</span>' + 
428
                           '<p>{4}</p>' +
429
                           '</li>').format(img.escape("name"), 
430
                                                  img.id, 
431
                                                  snf.ui.helpers.os_icon_tag(img.escape("OS")),
432
                                                  _.escape(img.get_readable_size()),
433
                                                  util.truncate(img.escape("description"), 35),
434
                                                  _.escape(img.display_owner())));
435
            image.data("image", img);
436
            image.data("image_id", img.id);
437
            this.images_list.append(image);
438
            image.find(".show-details").click(_.bind(function(e){
439
                e.preventDefault();
440
                e.stopPropagation();
441
                this.show_image_details(img);
442
            }, this))
443
        },
444
            
445
        hide_image_details: function() {
446
            this.image_details.fadeOut(200);
447
            this.parent.$(".create-controls").show();
448
        },
449

    
450
        show_image_details: function(img) {
451
            this.parent.$(".create-controls").hide();
452
            this.update_image_details(img);
453
            this.image_details.fadeIn(100);
454
        },
455

    
456
        reset: function() {
457
            this.select_type("system");
458
        },
459

    
460
        get: function() {
461
            return {'image': this.selected_image};
462
        },
463

    
464
        validate: function() {
465
            if (!this.selected_image) {
466
                this.parent.$(".form-action.next").hide();
467
            } else {
468
                this.parent.$(".form-action.next").show();
469
            }
470
        }
471
    });
472

    
473
    views.CreateFlavorSelectView = views.CreateVMStepView.extend({
474
        step: 2,
475
        initialize: function() {
476
            views.CreateFlavorSelectView.__super__.initialize.apply(this, arguments);
477
            this.parent.bind("image:change", _.bind(this.handle_image_change, this));
478

    
479
            this.cpus = this.$(".flavors-cpu-list");
480
            this.disks = this.$(".flavors-disk-list");
481
            this.disk_templates = this.$(".flavors-disk-template-list");
482
            this.mems = this.$(".flavors-mem-list");
483

    
484
            this.predefined_flavors = SUGGESTED_FLAVORS;
485
            this.predefined_flavors_keys = _.keys(SUGGESTED_FLAVORS);
486
            this.predefined_flavors_keys = _.sortBy(this.predefined_flavors_keys, _.bind(function(k){
487
                var flv = this.predefined_flavors[k];
488
                return (flv.ram * flv.cpu * flv.disk) + flv.disk_template;
489
            }, this));
490

    
491
            this.predefined = this.$(".predefined-list");
492
            this.update_predefined_flavors();
493
        },
494

    
495
        handle_image_change: function(data) {
496
            this.current_image = data;
497
            this.update_valid_predefined();
498
            this.update_flavors_data();
499
            this.reset_flavors();
500
            this.update_layout();
501
        },
502

    
503
        validate_selected_flavor: function() {
504
            if (!this.flavor_is_valid(this.current_flavor)) {
505
                this.select_valid_flavor();
506
            }
507
        },
508

    
509
        reset_flavors: function() {
510
            this.$(".flavor-opts-list .option").remove();
511
            this.create_flavors();
512
        },
513

    
514
        update_predefined_flavors: function() {
515
            this.predefined.find("li").remove();
516
            _.each(this.predefined_flavors_keys, _.bind(function(key) {
517
                var val = this.predefined_flavors[key];
518
                var el = $(('<li class="predefined-selection" id="predefined-flavor-{0}">' +
519
                           '{1}</li>').format(key, _.escape(_(key).capitalize())));
520

    
521
                this.predefined.append(el);
522
                el.data({flavor: storage.flavors.get_flavor(val.cpu, val.ram, val.disk, val.disk_template, this.flavors)});
523
                el.click(_.bind(function() {
524
                    this.handle_predefined_click(el);
525
                }, this))
526
            }, this));
527
            this.update_valid_predefined();
528
        },
529

    
530
        handle_predefined_click: function(el) {
531
            if (el.hasClass("disabled")) { return };
532
            this.set_current(el.data("flavor"));
533
        },
534

    
535
        select_valid_flavor: function() {
536
            var found = false;
537
            var self = this;
538
            _.each(this.flavors, function(flv) {
539
                if (self.flavor_is_valid(flv)) {
540
                    found = flv;
541
                    return false;
542
                }
543
            });
544
            
545
            if (found) {
546
                this.set_current(found);
547
            } else {
548
                this.current_flavor = undefined;
549
                this.validate();
550
                this.$("li.predefined-selection").addClass("disabled");
551
                this.$(".flavor-opts-list li").removeClass("selected");
552
            }
553
        },
554

    
555
        update_valid_predefined: function() {
556
            this.update_unavailable_values();
557
            var self = this;
558
            this.valid_predefined = _.select(_.map(this.predefined_flavors, function(flv, key){
559
                var existing = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, flv.disk_template, self.flavors);
560
                // non existing
561
                if (!existing) {
562
                    return false;
563
                }
564
                
565
                // not available for image
566
                if (self.unavailable_values && self.unavailable_values.disk.indexOf(existing.get_disk_size()) > -1) {
567
                    return false
568
                }
569

    
570
                return key;
571
            }), function(ret) { return ret });
572
            
573
            $("li.predefined-selection").addClass("disabled");
574
            _.each(this.valid_predefined, function(key) {
575
                $("#predefined-flavor-" + key).removeClass("disabled");
576
            })
577
        },
578

    
579
        update_selected_predefined: function() {
580
            var self = this;
581
            this.predefined.find("li").removeClass("selected");
582

    
583
            _.each(this.valid_predefined, function(key){
584
                var flv = self.predefined_flavors[key];
585
                var exists = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, flv.disk_template, self.flavors);
586

    
587
                if (exists && (exists.id == self.current_flavor.id)) {
588
                    $("#predefined-flavor-" + key).addClass("selected");
589
                }
590
            })
591
        },
592
        
593
        update_flavors_data: function() {
594
            this.flavors = storage.flavors.active();
595
            this.flavors_data = storage.flavors.get_data(this.flavors);
596
            
597
            var self = this;
598
            var set = false;
599
            
600
            // FIXME: validate current flavor
601
            
602
            if (!this.current_flavor) {
603
                _.each(this.valid_predefined, function(key) {
604
                    var flv = self.predefined_flavors[key];
605
                    var exists = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, flv.disk_template, self.flavors);
606
                    if (exists && !set) {
607
                        self.set_current(exists);
608
                        set = true;
609
                    }
610
                })
611
            }
612

    
613
            this.update_unavailable_values();
614
        },
615

    
616
        update_unavailable_values: function() {
617
            if (!this.current_image) { this.unavailable_values = {disk:[], ram:[], cpu:[]}; return };
618
            this.unavailable_values = storage.flavors.unavailable_values_for_image(this.current_image);
619
        },
620
        
621
        flavor_is_valid: function(flv) {
622
            if (!flv) { return false };
623

    
624
            var existing = storage.flavors.get_flavor(flv.get("cpu"), flv.get("ram"), flv.get("disk"), flv.get("disk_template"), this.flavors);
625
            if (!existing) { return false };
626
            
627
            if (this.unavailable_values && (this.unavailable_values.disk.indexOf(parseInt(flv.get("disk")) * 1000) > -1)) {
628
                return false;
629
            }
630
            return true;
631
        },
632
            
633
        set_valid_current_for: function(t, val) {
634
            var found = this.flavors[0];
635
            _.each(this.flavors, function(flv) {
636
                if (flv.get(t) == val) {
637
                    found = flv;
638
                }
639
            });
640

    
641
            this.set_current(found);
642
            this.validate_selected_flavor();
643
        },
644

    
645
        set_current: function(flv) {
646

    
647
            if (!flv) {
648
                // user clicked on invalid combination
649
                // force the first available choice for the
650
                // type of option he last clicked
651
                this.set_valid_current_for.apply(this, this.last_choice);
652
                return;
653
            }
654

    
655
            this.current_flavor = flv;
656
            this.trigger("change");
657
            if (this.current_flavor) {
658
                this.update_selected_flavor();
659
                this.update_selected_predefined();
660
            }
661
            
662
            this.validate();
663
        },
664
        
665
        select_default_flavor: function() {
666
               
667
        },
668

    
669
        update_selected_from_ui: function() {
670
            this.set_current(this.ui_selected());
671
        },
672
        
673
        update_disabled_flavors: function() {
674
            this.$(".flavor-options.disk li").removeClass("disabled");
675
            if (!this.unavailable_values) { return }
676
            
677
            this.$("#create-vm-flavor-options .flavor-options.disk li").each(_.bind(function(i, el){
678
                var el_value = $(el).data("value") * 1000;
679
                if (this.unavailable_values.disk.indexOf(el_value) > -1) {
680
                    $(el).addClass("disabled");
681
                };
682
            }, this));
683
        },
684

    
685
        create_flavors: function() {
686
            var flavors = this.get_active_flavors();
687
            var valid_flavors = this.get_valid_flavors();
688
            this.__added_flavors = {'cpu':[], 'ram':[], 'disk':[], 'disk_template':[] };
689

    
690
            _.each(flavors, _.bind(function(flv){
691
                this.add_flavor(flv);
692
            }, this));
693
            
694
            this.sort_flavors(this.disks);
695
            this.sort_flavors(this.cpus);
696
            this.sort_flavors(this.mems);
697
            this.sort_flavors(this.disk_templates);
698

    
699
            var self = this;
700
            this.$(".flavor-options li.option").click(function(){
701
                var el = $(this);
702

    
703
                if (el.hasClass("disabled")) { return }
704

    
705
                el.parent().find(".option").removeClass("selected");
706
                el.addClass("selected");
707
                
708
                if (el.hasClass("mem")) { self.last_choice = ["ram", $(this).data("value")] }
709
                if (el.hasClass("cpu")) { self.last_choice = ["cpu", $(this).data("value")] }
710
                if (el.hasClass("disk")) { self.last_choice = ["disk", $(this).data("value")] }
711
                if (el.hasClass("disk_template")) { self.last_choice = ["disk_template", $(this).data("value")] }
712

    
713
                self.update_selected_from_ui();
714
            })
715

    
716
            //this.$(".flavor-options li.disk_template.option").mouseover(function(){
717
                //$(this).parent().find(".description").hide();
718
                //$(this).find(".description").show();
719
            //}).mouseout(function(){
720
                //$(this).parent().find(".description").hide();
721
                //$(this).parent().find(".selected .description").show();
722
            //});
723
        },
724

    
725
        sort_flavors: function(els) {
726
            var prev = undefined;
727
            els.find("li").each(function(i,el){
728
                el = $(el);
729
                if (!prev) { prev = el; return true };
730
                if (el.data("value") < prev.data("value")) {
731
                    prev.before(el);
732
                }
733
                prev = el;
734
            })
735
        },
736
        
737
        ui_selected: function() {
738
            var args = [this.$(".option.cpu.selected").data("value"), 
739
                this.$(".option.mem.selected").data("value"), 
740
                this.$(".option.disk.selected").data("value"),
741
                this.$(".option.disk_template.selected").data("value"),
742
            this.flavors];
743

    
744
            var flv = storage.flavors.get_flavor.apply(storage.flavors, args);
745
            return flv;
746
        },
747

    
748
        update_selected_flavor: function() {
749
            var flv = this.current_flavor;
750
            if (!flv) { return }
751
            this.$(".option").removeClass("selected");
752

    
753
            this.$(".option.cpu.value-" + flv.get("cpu")).addClass("selected");
754
            this.$(".option.mem.value-" + flv.get("ram")).addClass("selected");
755
            this.$(".option.disk.value-" + flv.get("disk")).addClass("selected");
756
            this.$(".option.disk_template.value-" + flv.get("disk_template")).addClass("selected");
757
            
758
            var disk_el = this.$(".option.disk_template.value-" + flv.get("disk_template"));
759
            var basebgpos = 470;
760
                
761
            var append_to_bg_pos = 40 + (disk_el.index() * 91);
762
            var bg_pos = basebgpos - append_to_bg_pos;
763

    
764
            this.$(".disk-template-description").css({backgroundPosition:'-' + bg_pos + 'px top'})
765
            this.$(".disk-template-description p").html(flv.get_disk_template_info().description || "");
766
        },
767
        
768
        __added_flavors: {'cpu':[], 'ram':[], 'disk':[], 'disk_template':[]},
769
        add_flavor: function(flv) {
770
            var values = {'cpu': flv.get('cpu'), 
771
                          'mem': flv.get('ram'), 
772
                          'disk': flv.get('disk'), 
773
                          'disk_template': flv.get('disk_template')};
774

    
775
            disabled = "";
776
            
777
            if (this.__added_flavors.cpu.indexOf(values.cpu) == -1) {
778
                var cpu = $(('<li class="option cpu value-{0} {1}">' + 
779
                             '<span class="value">{0}</span>' + 
780
                             '<span class="metric">x</span></li>').format(
781
                            _.escape(values.cpu), disabled)).data('value', values.cpu);
782
                this.cpus.append(cpu);
783
                this.__added_flavors.cpu.push(values.cpu);
784
            }
785

    
786
            if (this.__added_flavors.ram.indexOf(values.mem) == -1) {
787
                var mem = $(('<li class="option mem value-{0}">' + 
788
                             '<span class="value">{0}</span>' + 
789
                             '<span class="metric">MB</span></li>').format(
790
                            _.escape(values.mem))).data('value', values.mem);
791
                this.mems.append(mem);
792
                this.__added_flavors.ram.push(values.mem);
793
            }
794

    
795
            if (this.__added_flavors.disk.indexOf(values.disk) == -1) {
796
                var disk = $(('<li class="option disk value-{0}">' + 
797
                              '<span class="value">{0}</span>' + 
798
                              '<span class="metric">GB</span></li>').format(
799
                            _.escape(values.disk))).data('value', values.disk);
800
                this.disks.append(disk);
801
                this.__added_flavors.disk.push(values.disk)
802
            }
803
            
804
            if (this.__added_flavors.disk_template.indexOf(values.disk_template) == -1) {
805
                var template_info = flv.get_disk_template_info();
806
                var disk_template = $(('<li title="{2}" class="option disk_template value-{0}">' + 
807
                                       '<span class="value name">{1}</span>' +
808
                                       '</li>').format(values.disk_template, 
809
                                            _.escape(template_info.name), 
810
                                            template_info.description)).data('value', 
811
                                                                values.disk_template);
812

    
813
                this.disk_templates.append(disk_template);
814
                //disk_template.tooltip({position:'top center', offset:[-5,0], delay:100, tipClass:'tooltip disktip'});
815
                this.__added_flavors.disk_template.push(values.disk_template)
816
            }
817
            
818
        },
819
        
820
        get_active_flavors: function() {
821
            return storage.flavors.active();
822
        },
823

    
824
        get_valid_flavors: function() {
825
            return this.flavors;
826
        },
827

    
828
        update_layout: function() {
829
            this.update_selected_flavor();
830
            this.update_disabled_flavors();
831
            this.validate();
832
            this.validate_selected_flavor();
833
        },
834

    
835
        reset: function() {
836
            this.current_image = storage.images.at(0);
837
            this.flavors = [];
838
            this.flavors_data = {'cpu':[], 'mem':[], 'disk':[]};
839
            this.update_flavors_data();
840
        },
841

    
842
        validate: function() {
843
            if (!this.current_flavor) {
844
                this.parent.$(".form-action.next").hide();
845
            } else {
846
                this.parent.$(".form-action.next").show();
847
            }
848
        },
849

    
850
        get: function() {
851
            return {'flavor': this.current_flavor}
852
        }
853

    
854
    });
855

    
856
    views.CreatePersonalizeView = views.CreateVMStepView.extend({
857
        step: 3,
858
        initialize: function() {
859
            views.CreateSubmitView.__super__.initialize.apply(this, arguments);
860
            this.roles = this.$("li.predefined-meta.role .values");
861
            this.name = this.$("input.rename-field");
862
            this.name_changed = false;
863
            this.init_suggested_roles();
864
            this.init_handlers();
865
            this.ssh_list = this.$(".ssh ul");
866
            this.selected_keys = [];
867

    
868
            var self = this;
869
            this.$(".create-ssh-key").click(function() {
870
                var confirm_close = true;
871
                if (confirm_close) {
872
                    snf.ui.main.public_keys_view.show(self.parent);
873
                } else {
874
                }
875
            });
876
        },
877

    
878
        init_suggested_roles: function() {
879
            var cont = this.roles;
880
            cont.empty();
881
            
882
            // TODO: get suggested from snf.api.conf
883
            _.each(window.SUGGESTED_ROLES, function(r){
884
                var el = $('<span class="val">{0}</span>'.format(_.escape(r)));
885
                el.data("value", r);
886
                cont.append(el);
887
                el.click(function() {
888
                    $(this).parent().find(".val").removeClass("selected");
889
                    $(this).toggleClass("selected");
890
                })
891
            });
892
            
893
            var self = this;
894
            $(".ssh li.ssh-key-option").live("click", function(e) {
895
                var key = $(this).data("model");
896
                self.select_key(key);
897
            });
898
        },
899

    
900
        select_key: function(key) {
901
            var exists = this.selected_keys.indexOf(key.id);
902
            if (exists > -1) {
903
                this.selected_keys.splice(exists, 1);
904
            } else {
905
                this.selected_keys.push(key.id);
906
            }
907
            this.update_ui_keys_selections(this.selected_keys);
908
        },
909

    
910
        update_ui_keys_selections: function(keys) {
911
            var self = this;
912
            self.$(".ssh-key-option").removeClass("selected");
913
            self.$(".ssh-key-option .check").attr("checked", false);
914
            _.each(keys, function(kid) {
915
                $("#ssh-key-option-" + kid).addClass("selected");
916
                $("#ssh-key-option-" + kid).find(".check").attr("checked", true);
917
            });
918
        },
919

    
920
        update_ssh_keys: function() {
921
            this.ssh_list.empty();
922
            var keys = snf.storage.keys.models;
923
            if (keys.length == 0) { 
924
                this.$(".ssh .empty").show();
925
            } else {
926
                this.$(".ssh .empty").hide();
927
            }
928
            _.each(keys, _.bind(function(key){
929
                var el = $('<li id="ssh-key-option-{1}" class="ssh-key-option">{0}</li>'.format(_.escape(key.get("name")), key.id));
930
                var check = $('<input class="check" type="checkbox"></input>')
931
                el.append(check);
932
                el.data("model", key);
933
                this.ssh_list.append(el);
934
            }, this));
935
        },
936

    
937
        init_handlers: function() {
938
            this.name.bind("keypress", _.bind(function(e) {
939
                this.name_changed = true;
940
                if (e.keyCode == 13) { this.parent.set_step(4); this.parent.update_layout() };    
941
            }, this));
942

    
943
            this.name.bind("click", _.bind(function() {
944
                if (!this.name_changed) {
945
                    this.name.val("");
946
                }
947
            }, this))
948
        },
949

    
950
        show: function() {
951
            views.CreatePersonalizeView.__super__.show.apply(this, arguments);
952
            this.update_layout();
953
        },
954
        
955
        update_layout: function() {
956
            var params = this.parent.get_params();
957

    
958
            if (!params.image || !params.flavor) { return }
959

    
960
            if (!params.image) { return }
961
            var vm_name_tpl = snf.config.vm_name_template || "My {0} server";
962
            var vm_name = vm_name_tpl.format(_.escape(params.image.get("name")));
963
            var orig_name = vm_name;
964
            
965
            var existing = true;
966
            var j = 0;
967

    
968
            while (existing && !this.name_changed) {
969
                var existing = storage.vms.select(function(vm){return vm.get("name") == vm_name}).length
970
                if (existing) {
971
                    j++;
972
                    vm_name = orig_name + " " + j;
973
                }
974
            }
975

    
976
            if (!_(this.name.val()).trim() || !this.name_changed) {
977
                this.name.val(vm_name);
978
            }
979

    
980
            if (!this.name_changed && this.parent.visible()) {
981
                if (!$.browser.msie && !$.browser.opera) {
982
                    this.$("#create-vm-name").select();
983
                } else {
984
                    window.setTimeout(_.bind(function(){
985
                        this.$("#create-vm-name").select();
986
                    }, this), 400)
987
                }
988
            }
989
            
990
            var img = snf.ui.helpers.os_icon_path(params.image.get("OS"))
991
            this.name.css({backgroundImage:"url({0})".format(img)})
992
            
993
            if (!params.image.supports('ssh')) {
994
                this.disable_ssh_keys();
995
            } else {
996
                this.enable_ssh_keys();
997
                this.update_ssh_keys();
998
            }
999

    
1000
            this.update_ui_keys_selections(this.selected_keys);
1001
        },
1002

    
1003
        disable_ssh_keys: function() {
1004
            this.$(".disabled.desc").show();
1005
            this.$(".empty.desc").hide();
1006
            this.$(".ssh .confirm-params").hide();
1007
            this.selected_keys = [];
1008
        },
1009

    
1010
        enable_ssh_keys: function() {
1011
            this.$(".ssh .confirm-params").show();
1012
            this.$(".disabled.desc").hide();
1013
        },
1014

    
1015
        reset: function() {
1016
            this.roles.find(".val").removeClass("selected");
1017
            this.name_changed = false;
1018
            this.selected_keys = [];
1019
            this.update_layout();
1020
        },
1021

    
1022
        get_meta: function() {
1023
            if (this.roles.find(".selected").length == 0) {
1024
                return false;
1025
            }
1026

    
1027
            var role = $(this.roles.find(".selected").get(0)).data("value");
1028
            return {'Role': role }
1029
        },
1030

    
1031
        get: function() {
1032
            var val = {'name': this.name.val() };
1033
            if (this.get_meta()) {
1034
                val.metadata = this.get_meta();
1035
            }
1036

    
1037
            val.keys = _.map(this.selected_keys, function(k){ return snf.storage.keys.get(k)});
1038
            
1039
            return val;
1040
        }
1041
    });
1042

    
1043
    views.CreateSubmitView = views.CreateVMStepView.extend({
1044
        step: 4,
1045
        initialize: function() {
1046
            views.CreateSubmitView.__super__.initialize.apply(this, arguments);
1047
            this.roles = this.$("li.predefined-meta.role .values");
1048
            this.confirm = this.$(".confirm-params ul");
1049
            this.name = this.$("h3.vm-name");
1050
            this.keys = this.$(".confirm-params.ssh");
1051
            this.meta = this.$(".confirm-params.meta");
1052
            this.init_handlers();
1053
        },
1054

    
1055
        init_handlers: function() {
1056
        },
1057

    
1058
        show: function() {
1059
            views.CreateSubmitView.__super__.show.apply(this, arguments);
1060
            this.update_layout();
1061
        },
1062
        
1063
        update_flavor_details: function() {
1064
            var flavor = this.parent.get_params().flavor;
1065

    
1066
            function set_detail(sel, key) {
1067
                var val = key;
1068
                if (key == undefined) { val = flavor.get(sel) };
1069
                this.$(".confirm-cont.flavor .flavor-" + sel + " .value").text(val)
1070
            }
1071
            
1072
            set_detail("cpu", flavor.get("cpu") + "x");
1073
            set_detail("ram", flavor.get("ram") + " MB");
1074
            set_detail("disk", util.readablizeBytes(flavor.get("disk") * 1024 * 1024 * 1024));
1075
            set_detail("disktype", flavor.get_disk_template_info().name);
1076
        },
1077

    
1078
        update_image_details: function() {
1079
            var image = this.parent.get_params().image;
1080

    
1081
            function set_detail(sel, key) {
1082
                var val = key;
1083
                if (key == undefined) { val = image.get(sel) };
1084
                this.$(".confirm-cont.image .image-" + sel + " .value").text(val)
1085
            }
1086
            
1087
            set_detail("description");
1088
            set_detail("name");
1089
            set_detail("os", _(image.escape("OS")).capitalize());
1090
            set_detail("gui", image.get("GUI"));
1091
            set_detail("size", _.escape(image.get_readable_size()));
1092
            set_detail("kernel");
1093
        },
1094

    
1095
        update_selected_keys: function(keys) {
1096
            this.keys.empty();
1097
            if (!keys || keys.length == 0) {
1098
                this.keys.append(this.make("li", {'class':'empty'}, 'No keys selected'))
1099
            }
1100
            _.each(keys, _.bind(function(key) {
1101
                var el = this.make("li", {'class':'selected-ssh-key'}, key.get('name'));
1102
                this.keys.append(el);
1103
            }, this))
1104
        },
1105

    
1106
        update_selected_meta: function(meta) {
1107
            this.meta.empty();
1108
            if (!meta || meta.length == 0) {
1109
                this.meta.append(this.make("li", {'class':'empty'}, 'No tags selected'))
1110
            }
1111
            _.each(meta, _.bind(function(value, key) {
1112
                var el = this.make("li", {'class':"confirm-value"});
1113
                var name = this.make("span", {'class':"ckey"}, key);
1114
                var value = this.make("span", {'class':"cval"}, value);
1115

    
1116
                $(el).append(name)
1117
                $(el).append(value);
1118
                this.meta.append(el);
1119
            }, this));
1120
        },
1121

    
1122
        update_layout: function() {
1123
            var params = this.parent.get_params();
1124
            if (!params.image || !params.flavor) { return }
1125

    
1126
            if (!params.image) { return }
1127

    
1128
            this.name.text(params.name);
1129

    
1130
            this.confirm.find("li.image .value").text(params.flavor.get("image"));
1131
            this.confirm.find("li.cpu .value").text(params.flavor.get("cpu") + "x");
1132
            this.confirm.find("li.mem .value").text(params.flavor.get("ram"));
1133
            this.confirm.find("li.disk .value").text(params.flavor.get("disk"));
1134

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

    
1138
            this.update_image_details();
1139
            this.update_flavor_details();
1140

    
1141
            if (!params.image.supports('ssh')) {
1142
                this.keys.hide();
1143
                this.keys.prev().hide();
1144
            } else {
1145
                this.keys.show();
1146
                this.keys.prev().show();
1147
                this.update_selected_keys(params.keys);
1148
            }
1149
            
1150
            this.update_selected_meta(params.metadata);
1151
        },
1152

    
1153
        reset: function() {
1154
            this.update_layout();
1155
        },
1156

    
1157
        get_meta: function() {
1158
        },
1159

    
1160
        get: function() {
1161
            return {};
1162
        }
1163
    });
1164

    
1165
    views.CreateVMView = views.Overlay.extend({
1166
        
1167
        view_id: "create_vm_view",
1168
        content_selector: "#createvm-overlay-content",
1169
        css_class: 'overlay-createvm overlay-info',
1170
        overlay_id: "metadata-overlay",
1171

    
1172
        subtitle: false,
1173
        title: "Create new machine",
1174

    
1175
        initialize: function(options) {
1176
            views.CreateVMView.__super__.initialize.apply(this);
1177
            this.current_step = 1;
1178

    
1179
            this.password_view = new views.VMCreationPasswordView();
1180

    
1181
            this.steps = [];
1182
            this.steps[1] = new views.CreateImageSelectView(this);
1183
            this.steps[1].bind("change", _.bind(function(data) {this.trigger("image:change", data)}, this));
1184

    
1185
            this.steps[2] = new views.CreateFlavorSelectView(this);
1186
            this.steps[3] = new views.CreatePersonalizeView(this);
1187
            this.steps[4] = new views.CreateSubmitView(this);
1188

    
1189
            this.cancel_btn = this.$(".create-controls .cancel");
1190
            this.next_btn = this.$(".create-controls .next");
1191
            this.prev_btn = this.$(".create-controls .prev");
1192
            this.submit_btn = this.$(".create-controls .submit");
1193

    
1194
            this.history = this.$(".steps-history");
1195
            this.history_steps = this.$(".steps-history .steps-history-step");
1196
            
1197
            this.init_handlers();
1198
        },
1199

    
1200
        init_handlers: function() {
1201
            var self = this;
1202
            this.next_btn.click(_.bind(function(){
1203
                this.set_step(this.current_step + 1);
1204
                this.update_layout();
1205
            }, this))
1206
            this.prev_btn.click(_.bind(function(){
1207
                this.set_step(this.current_step - 1);
1208
                this.update_layout();
1209
            }, this))
1210
            this.cancel_btn.click(_.bind(function(){
1211
                this.close_all();
1212
            }, this))
1213
            this.submit_btn.click(_.bind(function(){
1214
                this.submit();
1215
            }, this))
1216
            
1217
            this.history.find(".completed").live("click", function() {
1218
                var step = parseInt($(this).attr("id").replace("vm-create-step-history-", ""));
1219
                self.set_step(step);
1220
                self.update_layout();
1221
            })
1222
        },
1223

    
1224
        set_step: function(st) {
1225
        },
1226
        
1227
        validate: function(data) {
1228
            if (_(data.name).trim() == "") {
1229
                this.$(".form-field").addClass("error");
1230
                return false;
1231
            } else {
1232
                return true;
1233
            }
1234
        },
1235

    
1236
        submit: function() {
1237
            if (this.submiting) { return };
1238
            var data = this.get_params();
1239
            var meta = {};
1240
            var extra = {};
1241
            var personality = [];
1242

    
1243
            if (this.validate(data)) {
1244
                this.submit_btn.addClass("in-progress");
1245
                this.submiting = true;
1246
                if (data.metadata) { meta = data.metadata; }
1247
                if (data.keys && data.keys.length > 0) {
1248
                    personality.push(data.image.personality_data_for_keys(data.keys))
1249
                }
1250

    
1251
                if (personality.length) {
1252
                    extra['personality'] = personality;
1253
                }
1254

    
1255
                storage.vms.create(data.name, data.image, data.flavor, meta, extra, _.bind(function(data){
1256
                    this.close_all();
1257
                    this.password_view.show(data.server.adminPass, data.server.id);
1258
                    this.submiting = false;
1259
                }, this));
1260
            }
1261
        },
1262

    
1263
        close_all: function() {
1264
            this.hide();
1265
        },
1266

    
1267
        reset: function() {
1268
            this.current_step = 1;
1269

    
1270
            this.steps[1].reset();
1271
            this.steps[2].reset();
1272
            this.steps[3].reset();
1273
            this.steps[4].reset();
1274

    
1275
            this.steps[1].show();
1276
            this.steps[2].show();
1277
            this.steps[3].show();
1278
            this.steps[4].show();
1279

    
1280
            this.submit_btn.removeClass("in-progress");
1281
        },
1282

    
1283
        onShow: function() {
1284
        },
1285

    
1286
        update_layout: function() {
1287
            this.show_step(this.current_step);
1288
            this.current_view.update_layout();
1289
        },
1290

    
1291
        beforeOpen: function() {
1292
            if (!this.skip_reset_on_next_open) {
1293
                this.submiting = false;
1294
                this.reset();
1295
                this.current_step = 1;
1296
                this.$(".steps-container").css({"margin-left":0 + "px"});
1297
                this.show_step(1);
1298
            }
1299
            
1300
            this.skip_reset_on_next_open = false;
1301
            this.update_layout();
1302
        },
1303
        
1304
        set_step: function(step) {
1305
            if (step <= 1) {
1306
                step = 1
1307
            }
1308
            if (step > this.steps.length - 1) {
1309
                step = this.steps.length - 1;
1310
            }
1311
            this.current_step = step;
1312
        },
1313

    
1314
        show_step: function(step) {
1315
            // FIXME: this shouldn't be here
1316
            // but since we are not calling step.hide this should work
1317
            this.steps[1].image_details.hide();
1318

    
1319
            this.current_view = this.steps[step];
1320
            this.update_controls();
1321

    
1322
            this.steps[step].show();
1323
            var width = this.el.find('.container').width();
1324
            var left = (step -1) * width * -1;
1325
            this.$(".steps-container").animate({"margin-left": left + "px"}, 300);
1326

    
1327
            this.update_steps_history();
1328
        },
1329

    
1330
        update_steps_history: function() {
1331
            var self = this;
1332
            function get_step(s) {
1333
                return self.history.find(".step" + s + "h");
1334
            }
1335
            
1336
            var current_step = parseInt(this.current_view.step);
1337
            _.each(this.steps, function(stepv) {
1338
                var step = parseInt(stepv.step);
1339
                get_step(step).removeClass("completed").removeClass("current");
1340
                if (step == current_step) {
1341
                    get_step(step).removeClass("completed").addClass("current");
1342
                }
1343
                if (step < current_step) {
1344
                    get_step(step).removeClass("current").addClass("completed");
1345
                }
1346
            });
1347
        },
1348

    
1349
        update_controls: function() {
1350
            var step = this.current_step;
1351
            if (step == 1) {
1352
                this.prev_btn.hide();
1353
                this.cancel_btn.show();
1354
            } else {
1355
                this.prev_btn.show();
1356
                this.cancel_btn.hide();
1357
            }
1358
            
1359
            if (step == this.steps.length - 1) {
1360
                this.next_btn.hide();
1361
                this.submit_btn.show();
1362
            } else {
1363
                this.next_btn.show();
1364
                this.submit_btn.hide();
1365
            }
1366
        },
1367

    
1368
        get_params: function() {
1369
            return _.extend({}, this.steps[1].get(), this.steps[2].get(), this.steps[3].get());
1370
        }
1371
    });
1372
    
1373
})(this);
1374