Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (50.6 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

    
293
            $(".custom-image-help").hide();
294
            if (type == 'personal') {
295
                $(".custom-image-help").show();
296
            }
297

    
298
            this.update_layout_for_type(type);
299
        },
300

    
301
        update_layout_for_type: function(type) {
302
            if (type != "system") {
303
                this.$(".custom-action").hide();
304
            } else {
305
                this.$(".custom-action").hide();
306
            }
307

    
308
        },
309

    
310
        show_list_loading: function() {
311
            this.$(".images-list-cont").addClass("loading");
312
        },
313

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

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

    
336
            this.selected_image = image;
337
                
338
            if (image) {
339
                this.images_list.find(".image-details").removeClass("selected");
340
                this.images_list.find(".image-details#create-vm-image-" + this.selected_image.id).addClass("selected");
341
                this.update_image_details(image);
342

    
343
            } else {
344
            }
345

    
346
            this.image_details.hide();
347
            this.validate();
348
        },
349

    
350
        update_image_details: function(image) {
351
            this.image_details_desc.hide().parent().hide();
352
            if (image.get_description()) {
353
                this.image_details_desc.text(image.get_description(true)).show().parent().show();
354
            }
355
            var img = snf.ui.helpers.os_icon_tag(image.escape("OS"))
356
            if (image.get("name")) {
357
                this.image_details_title.html(img + image.escape("name")).show().parent().show();
358
            }
359
            
360
            var extra_details = this.image_details.find(".extra-details");
361
            // clean prevously added extra details
362
            extra_details.find(".image-detail").remove();
363
            
364
            var skip_keys = ['description', 'sortorder']
365
            var meta_keys = ['owner', 'OS', 'kernel', 'GUI'];
366
            var detail_tpl = ('<div class="clearfix image-detail {2}">' +
367
                             '<span class="title clearfix">{0}' +
368
                             '<span class="custom">custom</span></span>' +
369
                             '<p class="value">{1}</p>' + 
370
                             '</div>');
371
            meta_keys = _.union(meta_keys, this.images_storage.display_metadata || []);
372
            
373
            var append_metadata_row = function(key, is_extra) {
374
                var value;
375
                var method = 'get_' + key.toLowerCase();
376
                var display_method = 'display_' + key.toLowerCase();
377
                 
378
                if (image[display_method]) {
379
                    value = image[display_method]();
380
                } else if (image[method]) {
381
                    value = image[method]();
382
                } else {
383
                    value = image.get(key);
384

    
385
                    if (!value) {
386
                        value = image.get_meta(key);
387
                    }
388
                }
389
                    
390
                if (!value) { return; }
391
                 
392
                var label = this.images_storage.meta_labels[key];
393
                if (!label) {
394
                    var label = _(key.replace(/_/g," ")).capitalize();
395
                }
396
                var row_cls = key.toLowerCase();
397
                if (is_extra) { row_cls += " extra-meta" };
398
                extra_details.append(detail_tpl.format(_.escape(label), _.escape(value), row_cls));
399
            }
400

    
401
            _.each(meta_keys, function(key) {
402
                append_metadata_row.apply(this, [key]);
403
            }, this);
404
            
405
            if (synnefo.storage.images.display_extra_metadata) {
406
                _.each(image.get('metadata').values, function(value, key) {
407
                    if (!_.contains(meta_keys, key) && 
408
                        !_.contains(meta_keys, key.toLowerCase()) &&
409
                        !_.contains(meta_keys, key.toUpperCase()) &&
410
                        !_.contains(skip_keys, key)) {
411
                            append_metadata_row.apply(this, [key, true]);
412
                    }
413
                }, this);
414
            }
415
        },
416

    
417
        reset_images: function() {
418
            this.images_list.find("li").remove();
419
            _.each(this.images, _.bind(function(img){
420
                this.add_image(img);
421
            }, this))
422
            
423
            if (this.images.length) {
424
                this.images_list.parent().find(".empty").hide();
425
                this.images_list.show();
426
            } else {
427
                this.images_list.parent().find(".empty").show();
428
                this.images_list.hide();
429
            }
430

    
431
            var self = this;
432
            this.images_list.find(".image-details").click(function(){
433
                self.select_image($(this).data("image"));
434
            });
435
            
436
        },
437

    
438
        show: function() {
439
            this.image_details.hide();
440
            this.parent.$(".create-controls").show();
441

    
442
            views.CreateImageSelectView.__super__.show.apply(this, arguments);
443
        },
444

    
445
        add_image: function(img) {
446
            var image = $(('<li id="create-vm-image-{1}"' +
447
                           'class="image-details clearfix">{2}{0}'+
448
                           '<span class="show-details">details</span>'+
449
                           '<span class="size"><span class="prepend">by </span>{5}</span>' + 
450
                           '<span class="owner">' +
451
                           '<span class="prepend"></span>' +
452
                           '{3}</span>' + 
453
                           '<p>{4}</p>' +
454
                           '</li>').format(img.escape("name"), 
455
                                                  img.id, 
456
                                                  snf.ui.helpers.os_icon_tag(img.escape("OS")),
457
                                                  _.escape(img.get_readable_size()),
458
                                                  util.truncate(img.get_description(), 35),
459
                                                  _.escape(img.display_owner())));
460
            image.data("image", img);
461
            image.data("image_id", img.id);
462
            this.images_list.append(image);
463
            image.find(".show-details").click(_.bind(function(e){
464
                e.preventDefault();
465
                e.stopPropagation();
466
                this.show_image_details(img);
467
            }, this))
468
        },
469
            
470
        hide_image_details: function() {
471
            this.image_details.fadeOut(200);
472
            this.parent.$(".create-controls").show();
473
        },
474

    
475
        show_image_details: function(img) {
476
            this.parent.$(".create-controls").hide();
477
            this.update_image_details(img);
478
            this.image_details.fadeIn(100);
479
        },
480

    
481
        reset: function() {
482
            this.selected_image = false;
483
            this.select_type("system");
484
        },
485

    
486
        get: function() {
487
            return {'image': this.selected_image};
488
        },
489

    
490
        validate: function() {
491
            if (!this.selected_image) {
492
                this.parent.$(".form-action.next").hide();
493
            } else {
494
                this.parent.$(".form-action.next").show();
495
            }
496
        }
497
    });
498

    
499
    views.CreateFlavorSelectView = views.CreateVMStepView.extend({
500
        step: 2,
501
        initialize: function() {
502
            views.CreateFlavorSelectView.__super__.initialize.apply(this, arguments);
503
            this.parent.bind("image:change", _.bind(this.handle_image_change, this));
504

    
505
            this.cpus = this.$(".flavors-cpu-list");
506
            this.disks = this.$(".flavors-disk-list");
507
            this.disk_templates = this.$(".flavors-disk-template-list");
508
            this.mems = this.$(".flavors-mem-list");
509

    
510
            this.predefined_flavors = SUGGESTED_FLAVORS;
511
            this.predefined_flavors_keys = _.keys(SUGGESTED_FLAVORS);
512
            this.predefined_flavors_keys = _.sortBy(this.predefined_flavors_keys, _.bind(function(k){
513
                var flv = this.predefined_flavors[k];
514
                return (flv.ram * flv.cpu * flv.disk);
515
            }, this));
516

    
517
            this.predefined = this.$(".predefined-list");
518
            this.update_predefined_flavors();
519
        },
520

    
521
        handle_image_change: function(data) {
522
            this.current_image = data;
523
            this.update_valid_predefined();
524
            this.current_flavor = undefined;
525
            this.update_flavors_data();
526
            this.reset_flavors();
527
            this.update_layout();
528
        },
529

    
530
        validate_selected_flavor: function() {
531
            if (!this.flavor_is_valid(this.current_flavor)) {
532
                this.select_valid_flavor();
533
            }
534
        },
535

    
536
        reset_flavors: function() {
537
            this.$(".flavor-opts-list .option").remove();
538
            this.create_flavors();
539
        },
540

    
541
        update_predefined_flavors: function() {
542
            this.predefined.find("li").remove();
543
            _.each(this.predefined_flavors_keys, _.bind(function(key) {
544
                var val = this.predefined_flavors[key];
545
                var el = $(('<li class="predefined-selection" id="predefined-flavor-{0}">' +
546
                           '{1}</li>').format(key, _.escape(_(key).capitalize())));
547

    
548
                this.predefined.append(el);
549
                el.data({flavor: storage.flavors.get_flavor(val.cpu, val.ram, val.disk, val.disk_template, this.flavors)});
550
                el.click(_.bind(function() {
551
                    this.handle_predefined_click(el);
552
                }, this))
553
            }, this));
554
            this.update_valid_predefined();
555
        },
556

    
557
        handle_predefined_click: function(el) {
558
            if (el.hasClass("disabled")) { return };
559
            this.set_current(el.data("flavor"));
560
        },
561

    
562
        select_valid_flavor: function() {
563
            var found = false;
564
            var self = this;
565
            _.each(this.flavors, function(flv) {
566
                if (self.flavor_is_valid(flv)) {
567
                    found = flv;
568
                    return false;
569
                }
570
            });
571
            
572
            if (found) {
573
                this.set_current(found);
574
            } else {
575
                this.current_flavor = undefined;
576
                this.validate();
577
                this.$("li.predefined-selection").addClass("disabled");
578
                this.$(".flavor-opts-list li").removeClass("selected");
579
            }
580
        },
581

    
582
        update_valid_predefined: function() {
583
            this.update_unavailable_values();
584
            var self = this;
585
            this.valid_predefined = _.select(_.map(this.predefined_flavors, function(flv, key){
586
                var existing = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, flv.disk_template, self.flavors);
587
                // non existing
588
                if (!existing) {
589
                    return false;
590
                }
591
                
592
                // not available for image
593
                if (self.unavailable_values && self.unavailable_values.disk.indexOf(existing.get_disk_size()) > -1) {
594
                    return false
595
                }
596

    
597
                return key;
598
            }), function(ret) { return ret });
599
            
600
            $("li.predefined-selection").addClass("disabled");
601
            _.each(this.valid_predefined, function(key) {
602
                $("#predefined-flavor-" + key).removeClass("disabled");
603
            })
604
        },
605

    
606
        update_selected_predefined: function() {
607
            var self = this;
608
            this.predefined.find("li").removeClass("selected");
609

    
610
            _.each(this.valid_predefined, function(key){
611
                var flv = self.predefined_flavors[key];
612
                var exists = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, flv.disk_template, self.flavors);
613

    
614
                if (exists && (exists.id == self.current_flavor.id)) {
615
                    $("#predefined-flavor-" + key).addClass("selected");
616
                }
617
            })
618
        },
619
        
620
        update_flavors_data: function() {
621
            this.flavors = storage.flavors.active();
622
            this.flavors_data = storage.flavors.get_data(this.flavors);
623
            
624
            var self = this;
625
            var set = false;
626
            
627
            // FIXME: validate current flavor
628
            
629
            if (!this.current_flavor) {
630
                _.each(this.valid_predefined, function(key) {
631
                    var flv = self.predefined_flavors[key];
632
                    var exists = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, flv.disk_template, self.flavors);
633
                    if (exists && !set) {
634
                        self.set_current(exists);
635
                        set = true;
636
                    }
637
                })
638
            }
639

    
640
            this.update_unavailable_values();
641
        },
642

    
643
        update_unavailable_values: function() {
644
            if (!this.current_image) { this.unavailable_values = {disk:[], ram:[], cpu:[]}; return };
645
            this.unavailable_values = storage.flavors.unavailable_values_for_image(this.current_image);
646
        },
647
        
648
        flavor_is_valid: function(flv) {
649
            if (!flv) { return false };
650

    
651
            var existing = storage.flavors.get_flavor(flv.get("cpu"), flv.get("ram"), flv.get("disk"), flv.get("disk_template"), this.flavors);
652
            if (!existing) { return false };
653
            
654
            if (this.unavailable_values && (this.unavailable_values.disk.indexOf(parseInt(flv.get("disk")) * 1000) > -1)) {
655
                return false;
656
            }
657
            return true;
658
        },
659
            
660
        set_valid_current_for: function(t, val) {
661
            var found = this.flavors[0];
662
            _.each(this.flavors, function(flv) {
663
                if (flv.get(t) == val) {
664
                    found = flv;
665
                }
666
            });
667

    
668
            this.set_current(found);
669
            this.validate_selected_flavor();
670
        },
671

    
672
        set_current: function(flv) {
673

    
674
            if (!flv) {
675
                // user clicked on invalid combination
676
                // force the first available choice for the
677
                // type of option he last clicked
678
                this.set_valid_current_for.apply(this, this.last_choice);
679
                return;
680
            }
681

    
682
            this.current_flavor = flv;
683
            this.trigger("change");
684
            if (this.current_flavor) {
685
                this.update_selected_flavor();
686
                this.update_selected_predefined();
687
            }
688
            
689
            this.validate();
690
        },
691
        
692
        select_default_flavor: function() {
693
               
694
        },
695

    
696
        update_selected_from_ui: function() {
697
            this.set_current(this.ui_selected());
698
        },
699
        
700
        update_disabled_flavors: function() {
701
            this.$(".flavor-options.disk li").removeClass("disabled");
702
            if (!this.unavailable_values) { return }
703
            
704
            this.$("#create-vm-flavor-options .flavor-options.disk li").each(_.bind(function(i, el){
705
                var el_value = $(el).data("value") * 1000;
706
                if (this.unavailable_values.disk.indexOf(el_value) > -1) {
707
                    $(el).addClass("disabled");
708
                };
709
            }, this));
710
        },
711

    
712
        create_flavors: function() {
713
            var flavors = this.get_active_flavors();
714
            var valid_flavors = this.get_valid_flavors();
715
            this.__added_flavors = {'cpu':[], 'ram':[], 'disk':[], 'disk_template':[] };
716

    
717
            _.each(flavors, _.bind(function(flv){
718
                this.add_flavor(flv);
719
            }, this));
720
            
721
            this.sort_flavors(this.disks);
722
            this.sort_flavors(this.cpus);
723
            this.sort_flavors(this.mems);
724
            this.sort_flavors(this.disk_templates);
725

    
726
            var self = this;
727
            this.$(".flavor-options li.option").click(function(){
728
                var el = $(this);
729

    
730
                if (el.hasClass("disabled")) { return }
731

    
732
                el.parent().find(".option").removeClass("selected");
733
                el.addClass("selected");
734
                
735
                if (el.hasClass("mem")) { self.last_choice = ["ram", $(this).data("value")] }
736
                if (el.hasClass("cpu")) { self.last_choice = ["cpu", $(this).data("value")] }
737
                if (el.hasClass("disk")) { self.last_choice = ["disk", $(this).data("value")] }
738
                if (el.hasClass("disk_template")) { self.last_choice = ["disk_template", $(this).data("value")] }
739

    
740
                self.update_selected_from_ui();
741
            })
742

    
743
            //this.$(".flavor-options li.disk_template.option").mouseover(function(){
744
                //$(this).parent().find(".description").hide();
745
                //$(this).find(".description").show();
746
            //}).mouseout(function(){
747
                //$(this).parent().find(".description").hide();
748
                //$(this).parent().find(".selected .description").show();
749
            //});
750
        },
751

    
752
        sort_flavors: function(els) {
753
            var prev = undefined;
754
            els.find("li").each(function(i,el){
755
                el = $(el);
756
                if (!prev) { prev = el; return true };
757
                if (el.data("value") < prev.data("value")) {
758
                    prev.before(el);
759
                }
760
                prev = el;
761
            })
762
        },
763
        
764
        ui_selected: function() {
765
            var args = [this.$(".option.cpu.selected").data("value"), 
766
                this.$(".option.mem.selected").data("value"), 
767
                this.$(".option.disk.selected").data("value"),
768
                this.$(".option.disk_template.selected").data("value"),
769
            this.flavors];
770

    
771
            var flv = storage.flavors.get_flavor.apply(storage.flavors, args);
772
            return flv;
773
        },
774

    
775
        update_selected_flavor: function() {
776
            var flv = this.current_flavor;
777
            if (!flv) { return }
778
            this.$(".option").removeClass("selected");
779

    
780
            this.$(".option.cpu.value-" + flv.get("cpu")).addClass("selected");
781
            this.$(".option.mem.value-" + flv.get("ram")).addClass("selected");
782
            this.$(".option.disk.value-" + flv.get("disk")).addClass("selected");
783
            this.$(".option.disk_template.value-" + flv.get("disk_template")).addClass("selected");
784
            
785
            var disk_el = this.$(".option.disk_template.value-" + flv.get("disk_template"));
786
            var basebgpos = 470;
787
                
788
            var append_to_bg_pos = 40 + (disk_el.index() * 91);
789
            var bg_pos = basebgpos - append_to_bg_pos;
790

    
791
            this.$(".disk-template-description").css({backgroundPosition:'-' + bg_pos + 'px top'})
792
            this.$(".disk-template-description p").html(flv.get_disk_template_info().description || "");
793
        },
794
        
795
        __added_flavors: {'cpu':[], 'ram':[], 'disk':[], 'disk_template':[]},
796
        add_flavor: function(flv) {
797
            var values = {'cpu': flv.get('cpu'), 
798
                          'mem': flv.get('ram'), 
799
                          'disk': flv.get('disk'), 
800
                          'disk_template': flv.get('disk_template')};
801

    
802
            disabled = "";
803
            
804
            if (this.__added_flavors.cpu.indexOf(values.cpu) == -1) {
805
                var cpu = $(('<li class="option cpu value-{0} {1}">' + 
806
                             '<span class="value">{0}</span>' + 
807
                             '<span class="metric">x</span></li>').format(
808
                            _.escape(values.cpu), disabled)).data('value', values.cpu);
809
                this.cpus.append(cpu);
810
                this.__added_flavors.cpu.push(values.cpu);
811
            }
812

    
813
            if (this.__added_flavors.ram.indexOf(values.mem) == -1) {
814
                var mem = $(('<li class="option mem value-{0}">' + 
815
                             '<span class="value">{0}</span>' + 
816
                             '<span class="metric">MB</span></li>').format(
817
                            _.escape(values.mem))).data('value', values.mem);
818
                this.mems.append(mem);
819
                this.__added_flavors.ram.push(values.mem);
820
            }
821

    
822
            if (this.__added_flavors.disk.indexOf(values.disk) == -1) {
823
                var disk = $(('<li class="option disk value-{0}">' + 
824
                              '<span class="value">{0}</span>' + 
825
                              '<span class="metric">GB</span></li>').format(
826
                            _.escape(values.disk))).data('value', values.disk);
827
                this.disks.append(disk);
828
                this.__added_flavors.disk.push(values.disk)
829
            }
830
            
831
            if (this.__added_flavors.disk_template.indexOf(values.disk_template) == -1) {
832
                var template_info = flv.get_disk_template_info();
833
                var disk_template = $(('<li title="{2}" class="option disk_template value-{0}">' + 
834
                                       '<span class="value name">{1}</span>' +
835
                                       '</li>').format(values.disk_template, 
836
                                            _.escape(template_info.name), 
837
                                            template_info.description)).data('value', 
838
                                                                values.disk_template);
839

    
840
                this.disk_templates.append(disk_template);
841
                //disk_template.tooltip({position:'top center', offset:[-5,0], delay:100, tipClass:'tooltip disktip'});
842
                this.__added_flavors.disk_template.push(values.disk_template)
843
            }
844
            
845
        },
846
        
847
        get_active_flavors: function() {
848
            return storage.flavors.active();
849
        },
850

    
851
        get_valid_flavors: function() {
852
            return this.flavors;
853
        },
854

    
855
        update_layout: function() {
856
            this.update_selected_flavor();
857
            this.update_disabled_flavors();
858
            this.validate();
859
            this.validate_selected_flavor();
860
        },
861

    
862
        reset: function() {
863
            this.current_image = storage.images.at(0);
864
            this.flavors = [];
865
            this.flavors_data = {'cpu':[], 'mem':[], 'disk':[]};
866
            this.update_flavors_data();
867
        },
868

    
869
        validate: function() {
870
            if (!this.current_flavor) {
871
                this.parent.$(".form-action.next").hide();
872
            } else {
873
                this.parent.$(".form-action.next").show();
874
            }
875
        },
876

    
877
        get: function() {
878
            return {'flavor': this.current_flavor}
879
        }
880

    
881
    });
882

    
883
    views.CreatePersonalizeView = views.CreateVMStepView.extend({
884
        step: 3,
885
        initialize: function() {
886
            views.CreateSubmitView.__super__.initialize.apply(this, arguments);
887
            this.roles = this.$("li.predefined-meta.role .values");
888
            this.name = this.$("input.rename-field");
889
            this.name_changed = false;
890
            this.init_suggested_roles();
891
            this.init_handlers();
892
            this.ssh_list = this.$(".ssh ul");
893
            this.selected_keys = [];
894

    
895
            var self = this;
896
            this.$(".create-ssh-key").click(function() {
897
                var confirm_close = true;
898
                if (confirm_close) {
899
                    snf.ui.main.public_keys_view.show(self.parent);
900
                } else {
901
                }
902
            });
903
        },
904

    
905
        init_suggested_roles: function() {
906
            var cont = this.roles;
907
            cont.empty();
908
            
909
            // TODO: get suggested from snf.api.conf
910
            _.each(window.SUGGESTED_ROLES, function(r){
911
                var el = $('<span class="val">{0}</span>'.format(_.escape(r)));
912
                el.data("value", r);
913
                cont.append(el);
914
                el.click(function() {
915
                    $(this).parent().find(".val").removeClass("selected");
916
                    $(this).toggleClass("selected");
917
                })
918
            });
919
            
920
            var self = this;
921
            $(".ssh li.ssh-key-option").live("click", function(e) {
922
                var key = $(this).data("model");
923
                self.select_key(key);
924
            });
925
        },
926

    
927
        select_key: function(key) {
928
            var exists = this.selected_keys.indexOf(key.id);
929
            if (exists > -1) {
930
                this.selected_keys.splice(exists, 1);
931
            } else {
932
                this.selected_keys.push(key.id);
933
            }
934
            this.update_ui_keys_selections(this.selected_keys);
935
        },
936

    
937
        update_ui_keys_selections: function(keys) {
938
            var self = this;
939
            self.$(".ssh-key-option").removeClass("selected");
940
            self.$(".ssh-key-option .check").attr("checked", false);
941
            _.each(keys, function(kid) {
942
                $("#ssh-key-option-" + kid).addClass("selected");
943
                $("#ssh-key-option-" + kid).find(".check").attr("checked", true);
944
            });
945
        },
946

    
947
        update_ssh_keys: function() {
948
            this.ssh_list.empty();
949
            var keys = snf.storage.keys.models;
950
            if (keys.length == 0) { 
951
                this.$(".ssh .empty").show();
952
            } else {
953
                this.$(".ssh .empty").hide();
954
            }
955
            _.each(keys, _.bind(function(key){
956
                var el = $('<li id="ssh-key-option-{1}" class="ssh-key-option">{0}</li>'.format(_.escape(key.get("name")), key.id));
957
                var check = $('<input class="check" type="checkbox"></input>')
958
                el.append(check);
959
                el.data("model", key);
960
                this.ssh_list.append(el);
961
            }, this));
962
        },
963

    
964
        init_handlers: function() {
965
            this.name.bind("keypress", _.bind(function(e) {
966
                this.name_changed = true;
967
                if (e.keyCode == 13) { this.parent.set_step(4); this.parent.update_layout() };    
968
            }, this));
969

    
970
            this.name.bind("click", _.bind(function() {
971
                if (!this.name_changed) {
972
                    this.name.val("");
973
                }
974
            }, this))
975
        },
976

    
977
        show: function() {
978
            views.CreatePersonalizeView.__super__.show.apply(this, arguments);
979
            this.update_layout();
980
        },
981
        
982
        update_layout: function() {
983
            var params = this.parent.get_params();
984

    
985
            if (!params.image || !params.flavor) { return }
986

    
987
            if (!params.image) { return }
988
            var vm_name_tpl = snf.config.vm_name_template || "My {0} server";
989
            var vm_name = vm_name_tpl.format(_.escape(params.image.get("name")));
990
            var orig_name = vm_name;
991
            
992
            var existing = true;
993
            var j = 0;
994

    
995
            while (existing && !this.name_changed) {
996
                var existing = storage.vms.select(function(vm){return vm.get("name") == vm_name}).length
997
                if (existing) {
998
                    j++;
999
                    vm_name = orig_name + " " + j;
1000
                }
1001
            }
1002

    
1003
            if (!_(this.name.val()).trim() || !this.name_changed) {
1004
                this.name.val(vm_name);
1005
            }
1006

    
1007
            if (!this.name_changed && this.parent.visible()) {
1008
                if (!$.browser.msie && !$.browser.opera) {
1009
                    this.$("#create-vm-name").select();
1010
                } else {
1011
                    window.setTimeout(_.bind(function(){
1012
                        this.$("#create-vm-name").select();
1013
                    }, this), 400)
1014
                }
1015
            }
1016
            
1017
            var img = snf.ui.helpers.os_icon_path(params.image.get("OS"))
1018
            this.name.css({backgroundImage:"url({0})".format(img)})
1019
            
1020
            if (!params.image.supports('ssh')) {
1021
                this.disable_ssh_keys();
1022
            } else {
1023
                this.enable_ssh_keys();
1024
                this.update_ssh_keys();
1025
            }
1026

    
1027
            this.update_ui_keys_selections(this.selected_keys);
1028
        },
1029

    
1030
        disable_ssh_keys: function() {
1031
            this.$(".disabled.desc").show();
1032
            this.$(".empty.desc").hide();
1033
            this.$(".ssh .confirm-params").hide();
1034
            this.selected_keys = [];
1035
        },
1036

    
1037
        enable_ssh_keys: function() {
1038
            this.$(".ssh .confirm-params").show();
1039
            this.$(".disabled.desc").hide();
1040
        },
1041

    
1042
        reset: function() {
1043
            this.roles.find(".val").removeClass("selected");
1044
            this.name_changed = false;
1045
            this.selected_keys = [];
1046
            this.update_layout();
1047
        },
1048

    
1049
        get_meta: function() {
1050
            if (this.roles.find(".selected").length == 0) {
1051
                return false;
1052
            }
1053

    
1054
            var role = $(this.roles.find(".selected").get(0)).data("value");
1055
            return {'Role': role }
1056
        },
1057

    
1058
        get: function() {
1059
            var val = {'name': this.name.val() };
1060
            if (this.get_meta()) {
1061
                val.metadata = this.get_meta();
1062
            }
1063

    
1064
            val.keys = _.map(this.selected_keys, function(k){ return snf.storage.keys.get(k)});
1065
            
1066
            return val;
1067
        }
1068
    });
1069

    
1070
    views.CreateSubmitView = views.CreateVMStepView.extend({
1071
        step: 4,
1072
        initialize: function() {
1073
            views.CreateSubmitView.__super__.initialize.apply(this, arguments);
1074
            this.roles = this.$("li.predefined-meta.role .values");
1075
            this.confirm = this.$(".confirm-params ul");
1076
            this.name = this.$("h3.vm-name");
1077
            this.keys = this.$(".confirm-params.ssh");
1078
            this.meta = this.$(".confirm-params.meta");
1079
            this.init_handlers();
1080
        },
1081

    
1082
        init_handlers: function() {
1083
        },
1084

    
1085
        show: function() {
1086
            views.CreateSubmitView.__super__.show.apply(this, arguments);
1087
            this.update_layout();
1088
        },
1089
        
1090
        update_flavor_details: function() {
1091
            var flavor = this.parent.get_params().flavor;
1092

    
1093
            function set_detail(sel, key) {
1094
                var val = key;
1095
                if (key == undefined) { val = flavor.get(sel) };
1096
                this.$(".confirm-cont.flavor .flavor-" + sel + " .value").text(val)
1097
            }
1098
            
1099
            set_detail("cpu", flavor.get("cpu") + "x");
1100
            set_detail("ram", flavor.get("ram") + " MB");
1101
            set_detail("disk", util.readablizeBytes(flavor.get("disk") * 1024 * 1024 * 1024));
1102
            set_detail("disktype", flavor.get_disk_template_info().name);
1103
        },
1104

    
1105
        update_image_details: function() {
1106
            var image = this.parent.get_params().image;
1107

    
1108
            function set_detail(sel, key) {
1109
                var val = key;
1110
                if (key == undefined) { val = image.get(sel) };
1111
                this.$(".confirm-cont.image .image-" + sel + " .value").text(val)
1112
            }
1113
            
1114
            set_detail("description", image.get_description());
1115
            set_detail("name");
1116
            set_detail("os", _(image.get_os()).capitalize());
1117
            set_detail("gui", image.get_gui());
1118
            set_detail("size", _.escape(image.get_readable_size()));
1119
            set_detail("kernel");
1120
        },
1121

    
1122
        update_selected_keys: function(keys) {
1123
            this.keys.empty();
1124
            if (!keys || keys.length == 0) {
1125
                this.keys.append(this.make("li", {'class':'empty'}, 'No keys selected'))
1126
            }
1127
            _.each(keys, _.bind(function(key) {
1128
                var el = this.make("li", {'class':'selected-ssh-key'}, key.get('name'));
1129
                this.keys.append(el);
1130
            }, this))
1131
        },
1132

    
1133
        update_selected_meta: function(meta) {
1134
            this.meta.empty();
1135
            if (!meta || meta.length == 0) {
1136
                this.meta.append(this.make("li", {'class':'empty'}, 'No tags selected'))
1137
            }
1138
            _.each(meta, _.bind(function(value, key) {
1139
                var el = this.make("li", {'class':"confirm-value"});
1140
                var name = this.make("span", {'class':"ckey"}, key);
1141
                var value = this.make("span", {'class':"cval"}, value);
1142

    
1143
                $(el).append(name)
1144
                $(el).append(value);
1145
                this.meta.append(el);
1146
            }, this));
1147
        },
1148

    
1149
        update_layout: function() {
1150
            var params = this.parent.get_params();
1151
            if (!params.image || !params.flavor) { return }
1152

    
1153
            if (!params.image) { return }
1154

    
1155
            this.name.text(params.name);
1156

    
1157
            this.confirm.find("li.image .value").text(params.flavor.get("image"));
1158
            this.confirm.find("li.cpu .value").text(params.flavor.get("cpu") + "x");
1159
            this.confirm.find("li.mem .value").text(params.flavor.get("ram"));
1160
            this.confirm.find("li.disk .value").text(params.flavor.get("disk"));
1161

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

    
1165
            this.update_image_details();
1166
            this.update_flavor_details();
1167

    
1168
            if (!params.image.supports('ssh')) {
1169
                this.keys.hide();
1170
                this.keys.prev().hide();
1171
            } else {
1172
                this.keys.show();
1173
                this.keys.prev().show();
1174
                this.update_selected_keys(params.keys);
1175
            }
1176
            
1177
            this.update_selected_meta(params.metadata);
1178
        },
1179

    
1180
        reset: function() {
1181
            this.update_layout();
1182
        },
1183

    
1184
        get_meta: function() {
1185
        },
1186

    
1187
        get: function() {
1188
            return {};
1189
        }
1190
    });
1191

    
1192
    views.CreateVMView = views.Overlay.extend({
1193
        
1194
        view_id: "create_vm_view",
1195
        content_selector: "#createvm-overlay-content",
1196
        css_class: 'overlay-createvm overlay-info',
1197
        overlay_id: "metadata-overlay",
1198

    
1199
        subtitle: false,
1200
        title: "Create new machine",
1201

    
1202
        initialize: function(options) {
1203
            views.CreateVMView.__super__.initialize.apply(this);
1204
            this.current_step = 1;
1205

    
1206
            this.password_view = new views.VMCreationPasswordView();
1207

    
1208
            this.steps = [];
1209
            this.steps[1] = new views.CreateImageSelectView(this);
1210
            this.steps[1].bind("change", _.bind(function(data) {this.trigger("image:change", data)}, this));
1211

    
1212
            this.steps[2] = new views.CreateFlavorSelectView(this);
1213
            this.steps[3] = new views.CreatePersonalizeView(this);
1214
            this.steps[4] = new views.CreateSubmitView(this);
1215

    
1216
            this.cancel_btn = this.$(".create-controls .cancel");
1217
            this.next_btn = this.$(".create-controls .next");
1218
            this.prev_btn = this.$(".create-controls .prev");
1219
            this.submit_btn = this.$(".create-controls .submit");
1220

    
1221
            this.history = this.$(".steps-history");
1222
            this.history_steps = this.$(".steps-history .steps-history-step");
1223
            
1224
            this.init_handlers();
1225
        },
1226

    
1227
        init_handlers: function() {
1228
            var self = this;
1229
            this.next_btn.click(_.bind(function(){
1230
                this.set_step(this.current_step + 1);
1231
                this.update_layout();
1232
            }, this))
1233
            this.prev_btn.click(_.bind(function(){
1234
                this.set_step(this.current_step - 1);
1235
                this.update_layout();
1236
            }, this))
1237
            this.cancel_btn.click(_.bind(function(){
1238
                this.close_all();
1239
            }, this))
1240
            this.submit_btn.click(_.bind(function(){
1241
                this.submit();
1242
            }, this))
1243
            
1244
            this.history.find(".completed").live("click", function() {
1245
                var step = parseInt($(this).attr("id").replace("vm-create-step-history-", ""));
1246
                self.set_step(step);
1247
                self.update_layout();
1248
            })
1249
        },
1250

    
1251
        set_step: function(st) {
1252
        },
1253
        
1254
        validate: function(data) {
1255
            if (_(data.name).trim() == "") {
1256
                this.$(".form-field").addClass("error");
1257
                return false;
1258
            } else {
1259
                return true;
1260
            }
1261
        },
1262

    
1263
        submit: function() {
1264
            if (this.submiting) { return };
1265
            var data = this.get_params();
1266
            var meta = {};
1267
            var extra = {};
1268
            var personality = [];
1269

    
1270
            if (this.validate(data)) {
1271
                this.submit_btn.addClass("in-progress");
1272
                this.submiting = true;
1273
                if (data.metadata) { meta = data.metadata; }
1274
                if (data.keys && data.keys.length > 0) {
1275
                    personality.push(data.image.personality_data_for_keys(data.keys))
1276
                }
1277

    
1278
                if (personality.length) {
1279
                    extra['personality'] = personality;
1280
                }
1281

    
1282
                storage.vms.create(data.name, data.image, data.flavor, meta, extra, _.bind(function(data){
1283
                    this.close_all();
1284
                    this.password_view.show(data.server.adminPass, data.server.id);
1285
                    this.submiting = false;
1286
                }, this));
1287
            }
1288
        },
1289

    
1290
        close_all: function() {
1291
            this.hide();
1292
        },
1293

    
1294
        reset: function() {
1295
            this.current_step = 1;
1296

    
1297
            this.steps[1].reset();
1298
            this.steps[2].reset();
1299
            this.steps[3].reset();
1300
            this.steps[4].reset();
1301

    
1302
            this.steps[1].show();
1303
            this.steps[2].show();
1304
            this.steps[3].show();
1305
            this.steps[4].show();
1306

    
1307
            this.submit_btn.removeClass("in-progress");
1308
        },
1309

    
1310
        onShow: function() {
1311
        },
1312

    
1313
        update_layout: function() {
1314
            this.show_step(this.current_step);
1315
            this.current_view.update_layout();
1316
        },
1317

    
1318
        beforeOpen: function() {
1319
            if (!this.skip_reset_on_next_open) {
1320
                this.submiting = false;
1321
                this.reset();
1322
                this.current_step = 1;
1323
                this.$(".steps-container").css({"margin-left":0 + "px"});
1324
                this.show_step(1);
1325
            }
1326
            
1327
            this.skip_reset_on_next_open = false;
1328
            this.update_layout();
1329
        },
1330
        
1331
        set_step: function(step) {
1332
            if (step <= 1) {
1333
                step = 1
1334
            }
1335
            if (step > this.steps.length - 1) {
1336
                step = this.steps.length - 1;
1337
            }
1338
            this.current_step = step;
1339
        },
1340

    
1341
        show_step: function(step) {
1342
            // FIXME: this shouldn't be here
1343
            // but since we are not calling step.hide this should work
1344
            this.steps[1].image_details.hide();
1345

    
1346
            this.current_view = this.steps[step];
1347
            this.update_controls();
1348

    
1349
            this.steps[step].show();
1350
            var width = this.el.find('.container').width();
1351
            var left = (step -1) * width * -1;
1352
            this.$(".steps-container").animate({"margin-left": left + "px"}, 300);
1353

    
1354
            this.update_steps_history();
1355
        },
1356

    
1357
        update_steps_history: function() {
1358
            var self = this;
1359
            function get_step(s) {
1360
                return self.history.find(".step" + s + "h");
1361
            }
1362
            
1363
            var current_step = parseInt(this.current_view.step);
1364
            _.each(this.steps, function(stepv) {
1365
                var step = parseInt(stepv.step);
1366
                get_step(step).removeClass("completed").removeClass("current");
1367
                if (step == current_step) {
1368
                    get_step(step).removeClass("completed").addClass("current");
1369
                }
1370
                if (step < current_step) {
1371
                    get_step(step).removeClass("current").addClass("completed");
1372
                }
1373
            });
1374
        },
1375

    
1376
        update_controls: function() {
1377
            var step = this.current_step;
1378
            if (step == 1) {
1379
                this.prev_btn.hide();
1380
                this.cancel_btn.show();
1381
            } else {
1382
                this.prev_btn.show();
1383
                this.cancel_btn.hide();
1384
            }
1385
            
1386
            if (step == this.steps.length - 1) {
1387
                this.next_btn.hide();
1388
                this.submit_btn.show();
1389
            } else {
1390
                this.next_btn.show();
1391
                this.submit_btn.hide();
1392
            }
1393
        },
1394

    
1395
        get_params: function() {
1396
            return _.extend({}, this.steps[1].get(), this.steps[2].get(), this.steps[3].get());
1397
        }
1398
    });
1399
    
1400
})(this);
1401