Statistics
| Branch: | Tag: | Revision:

root / snf-app / synnefo / ui / static / snf / js / ui / web / ui_create_view.js @ 1faf0b9c

History | View | Annotate | Download (49.1 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
            
288
            var images = this.images_storage.update_images_for_type(
289
                this.selected_type, 
290
                _.bind(this.show_loading_view, this), 
291
                _.bind(this.hide_loading_view, this)
292
            );
293
            this.update_layout_for_type(type);
294
        },
295

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

    
303
        },
304

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

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

    
313
        select_image: function(image) {
314
            if (!image && this.images_ids.length) {
315
                if (this.selected_image && this.images_ids.indexOf(this.selected_image.id) > -1) {
316
                    image = this.selected_image;
317
                } else {
318
                    image = this.images_storage.get(this.images_ids[0]);
319
                }
320
            }
321
                
322
            if (!this.images_ids.length) { image = this.selected_image || undefined };
323
            
324
            if ((!this.selected_image && image) || (this.selected_image != image))
325
                this.trigger("change", image);
326

    
327
            this.selected_image = image;
328
                
329
            if (image) {
330
                this.images_list.find(".image-details").removeClass("selected");
331
                this.images_list.find(".image-details#create-vm-image-" + this.selected_image.id).addClass("selected");
332
                this.update_image_details(image);
333

    
334
            } else {
335
            }
336

    
337
            this.image_details.hide();
338
            this.validate();
339
        },
340

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

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

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

    
374
                    if (!value) {
375
                        value = image.get_meta(key);
376
                    }
377
                }
378

    
379
                if (!value) { return; }
380
                
381
                var label = _(key.replace(/_/g," ")).capitalize();
382
                extra_details.append(detail_tpl.format(_.escape(label), _.escape(value), key.toLowerCase()));
383
            })
384
        },
385

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

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

    
409
        show: function() {
410
            views.CreateImageSelectView.__super__.show.apply(this, arguments);
411
            this.image_details.hide();
412
        },
413

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

    
444
        show_image_details: function(img) {
445
            this.parent.$(".create-controls").hide();
446
            this.update_image_details(img);
447
            this.image_details.fadeIn(100);
448
        },
449

    
450
        reset: function() {
451
            this.select_type("system");
452
        },
453

    
454
        get: function() {
455
            return {'image': this.selected_image};
456
        },
457

    
458
        validate: function() {
459
            if (!this.selected_image) {
460
                this.parent.$(".form-action.next").hide();
461
            } else {
462
                this.parent.$(".form-action.next").show();
463
            }
464
        }
465
    });
466

    
467
    views.CreateFlavorSelectView = views.CreateVMStepView.extend({
468
        step: 2,
469
        initialize: function() {
470
            views.CreateFlavorSelectView.__super__.initialize.apply(this, arguments);
471
            this.parent.bind("image:change", _.bind(this.handle_image_change, this));
472

    
473
            this.cpus = this.$(".flavors-cpu-list");
474
            this.disks = this.$(".flavors-disk-list");
475
            this.disk_templates = this.$(".flavors-disk-template-list");
476
            this.mems = this.$(".flavors-mem-list");
477

    
478
            this.predefined_flavors = SUGGESTED_FLAVORS;
479
            this.predefined_flavors_keys = _.keys(SUGGESTED_FLAVORS);
480
            this.predefined_flavors_keys = _.sortBy(this.predefined_flavors_keys, _.bind(function(k){
481
                var flv = this.predefined_flavors[k];
482
                return (flv.ram * flv.cpu * flv.disk) + flv.disk_template;
483
            }, this));
484

    
485
            this.predefined = this.$(".predefined-list");
486
            this.update_predefined_flavors();
487
        },
488

    
489
        handle_image_change: function(data) {
490
            this.current_image = data;
491
            this.update_valid_predefined();
492
            this.update_flavors_data();
493
            this.reset_flavors();
494
            this.update_layout();
495
        },
496

    
497
        validate_selected_flavor: function() {
498
            if (!this.flavor_is_valid(this.current_flavor)) {
499
                this.select_valid_flavor();
500
            }
501
        },
502

    
503
        reset_flavors: function() {
504
            this.$(".flavor-opts-list .option").remove();
505
            this.create_flavors();
506
        },
507

    
508
        update_predefined_flavors: function() {
509
            this.predefined.find("li").remove();
510
            _.each(this.predefined_flavors_keys, _.bind(function(key) {
511
                var val = this.predefined_flavors[key];
512
                var el = $(('<li class="predefined-selection" id="predefined-flavor-{0}">' +
513
                           '{1}</li>').format(key, _.escape(_(key).capitalize())));
514

    
515
                this.predefined.append(el);
516
                el.data({flavor: storage.flavors.get_flavor(val.cpu, val.ram, val.disk, val.disk_template, this.flavors)});
517
                el.click(_.bind(function() {
518
                    this.handle_predefined_click(el);
519
                }, this))
520
            }, this));
521
            this.update_valid_predefined();
522
        },
523

    
524
        handle_predefined_click: function(el) {
525
            if (el.hasClass("disabled")) { return };
526
            this.set_current(el.data("flavor"));
527
        },
528

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

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

    
564
                return key;
565
            }), function(ret) { return ret });
566
            
567
            $("li.predefined-selection").addClass("disabled");
568
            _.each(this.valid_predefined, function(key) {
569
                $("#predefined-flavor-" + key).removeClass("disabled");
570
            })
571
        },
572

    
573
        update_selected_predefined: function() {
574
            var self = this;
575
            this.predefined.find("li").removeClass("selected");
576

    
577
            _.each(this.valid_predefined, function(key){
578
                var flv = self.predefined_flavors[key];
579
                var exists = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, flv.disk_template, self.flavors);
580

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

    
607
            this.update_unavailable_values();
608
        },
609

    
610
        update_unavailable_values: function() {
611
            if (!this.current_image) { this.unavailable_values = {disk:[], ram:[], cpu:[]}; return };
612
            this.unavailable_values = storage.flavors.unavailable_values_for_image(this.current_image);
613
        },
614
        
615
        flavor_is_valid: function(flv) {
616
            if (!flv) { return false };
617

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

    
635
            this.set_current(found);
636
            this.validate_selected_flavor();
637
        },
638

    
639
        set_current: function(flv) {
640

    
641
            if (!flv) {
642
                // user clicked on invalid combination
643
                // force the first available choice for the
644
                // type of option he last clicked
645
                this.set_valid_current_for.apply(this, this.last_choice);
646
                return;
647
            }
648

    
649
            this.current_flavor = flv;
650
            this.trigger("change");
651
            if (this.current_flavor) {
652
                this.update_selected_flavor();
653
                this.update_selected_predefined();
654
            }
655
            
656
            this.validate();
657
        },
658
        
659
        select_default_flavor: function() {
660
               
661
        },
662

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

    
679
        create_flavors: function() {
680
            var flavors = this.get_active_flavors();
681
            var valid_flavors = this.get_valid_flavors();
682
            this.__added_flavors = {'cpu':[], 'ram':[], 'disk':[], 'disk_template':[] };
683

    
684
            _.each(flavors, _.bind(function(flv){
685
                this.add_flavor(flv);
686
            }, this));
687
            
688
            this.sort_flavors(this.disks);
689
            this.sort_flavors(this.cpus);
690
            this.sort_flavors(this.mems);
691
            this.sort_flavors(this.disk_templates);
692

    
693
            var self = this;
694
            this.$(".flavor-options li.option").click(function(){
695
                var el = $(this);
696

    
697
                if (el.hasClass("disabled")) { return }
698

    
699
                el.parent().find(".option").removeClass("selected");
700
                el.addClass("selected");
701
                
702
                if (el.hasClass("mem")) { self.last_choice = ["ram", $(this).data("value")] }
703
                if (el.hasClass("cpu")) { self.last_choice = ["cpu", $(this).data("value")] }
704
                if (el.hasClass("disk")) { self.last_choice = ["disk", $(this).data("value")] }
705
                if (el.hasClass("disk_template")) { self.last_choice = ["disk_template", $(this).data("value")] }
706

    
707
                self.update_selected_from_ui();
708
            })
709

    
710
            //this.$(".flavor-options li.disk_template.option").mouseover(function(){
711
                //$(this).parent().find(".description").hide();
712
                //$(this).find(".description").show();
713
            //}).mouseout(function(){
714
                //$(this).parent().find(".description").hide();
715
                //$(this).parent().find(".selected .description").show();
716
            //});
717
        },
718

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

    
738
            var flv = storage.flavors.get_flavor.apply(storage.flavors, args);
739
            return flv;
740
        },
741

    
742
        update_selected_flavor: function() {
743
            var flv = this.current_flavor;
744
            if (!flv) { return }
745
            this.$(".option").removeClass("selected");
746

    
747
            this.$(".option.cpu.value-" + flv.get("cpu")).addClass("selected");
748
            this.$(".option.mem.value-" + flv.get("ram")).addClass("selected");
749
            this.$(".option.disk.value-" + flv.get("disk")).addClass("selected");
750
            this.$(".option.disk_template.value-" + flv.get("disk_template")).addClass("selected");
751
            
752
            var disk_el = this.$(".option.disk_template.value-" + flv.get("disk_template"));
753
            var basebgpos = 470;
754
                
755
            var append_to_bg_pos = 40 + (disk_el.index() * 91);
756
            var bg_pos = basebgpos - append_to_bg_pos;
757

    
758
            this.$(".disk-template-description").css({backgroundPosition:'-' + bg_pos + 'px top'})
759
            this.$(".disk-template-description p").html(flv.get_disk_template_info().description || "");
760
        },
761
        
762
        __added_flavors: {'cpu':[], 'ram':[], 'disk':[], 'disk_template':[]},
763
        add_flavor: function(flv) {
764
            var values = {'cpu': flv.get('cpu'), 
765
                          'mem': flv.get('ram'), 
766
                          'disk': flv.get('disk'), 
767
                          'disk_template': flv.get('disk_template')};
768

    
769
            disabled = "";
770
            
771
            if (this.__added_flavors.cpu.indexOf(values.cpu) == -1) {
772
                var cpu = $(('<li class="option cpu value-{0} {1}">' + 
773
                             '<span class="value">{0}</span>' + 
774
                             '<span class="metric">x</span></li>').format(
775
                            _.escape(values.cpu), disabled)).data('value', values.cpu);
776
                this.cpus.append(cpu);
777
                this.__added_flavors.cpu.push(values.cpu);
778
            }
779

    
780
            if (this.__added_flavors.ram.indexOf(values.mem) == -1) {
781
                var mem = $(('<li class="option mem value-{0}">' + 
782
                             '<span class="value">{0}</span>' + 
783
                             '<span class="metric">MB</span></li>').format(
784
                            _.escape(values.mem))).data('value', values.mem);
785
                this.mems.append(mem);
786
                this.__added_flavors.ram.push(values.mem);
787
            }
788

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

    
807
                this.disk_templates.append(disk_template);
808
                //disk_template.tooltip({position:'top center', offset:[-5,0], delay:100, tipClass:'tooltip disktip'});
809
                this.__added_flavors.disk_template.push(values.disk_template)
810
            }
811
            
812
        },
813
        
814
        get_active_flavors: function() {
815
            return storage.flavors.active();
816
        },
817

    
818
        get_valid_flavors: function() {
819
            return this.flavors;
820
        },
821

    
822
        update_layout: function() {
823
            this.update_selected_flavor();
824
            this.update_disabled_flavors();
825
            this.validate();
826
            this.validate_selected_flavor();
827
        },
828

    
829
        reset: function() {
830
            this.current_image = storage.images.at(0);
831
            this.flavors = [];
832
            this.flavors_data = {'cpu':[], 'mem':[], 'disk':[]};
833
            this.update_flavors_data();
834
        },
835

    
836
        validate: function() {
837
            if (!this.current_flavor) {
838
                this.parent.$(".form-action.next").hide();
839
            } else {
840
                this.parent.$(".form-action.next").show();
841
            }
842
        },
843

    
844
        get: function() {
845
            return {'flavor': this.current_flavor}
846
        }
847

    
848
    });
849

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

    
862
            var self = this;
863
            this.$(".create-ssh-key").click(function() {
864
                var confirm_close = true;
865
                if (confirm_close) {
866
                    snf.ui.main.public_keys_view.show(self.parent);
867
                } else {
868
                }
869
            });
870
        },
871

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

    
894
        select_key: function(key) {
895
            var exists = this.selected_keys.indexOf(key.id);
896
            if (exists > -1) {
897
                this.selected_keys.splice(exists, 1);
898
            } else {
899
                this.selected_keys.push(key.id);
900
            }
901
            this.update_ui_keys_selections(this.selected_keys);
902
        },
903

    
904
        update_ui_keys_selections: function(keys) {
905
            var self = this;
906
            self.$(".ssh-key-option").removeClass("selected");
907
            self.$(".ssh-key-option .check").attr("checked", false);
908
            _.each(keys, function(kid) {
909
                $("#ssh-key-option-" + kid).addClass("selected");
910
                $("#ssh-key-option-" + kid).find(".check").attr("checked", true);
911
            });
912
        },
913

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

    
931
        init_handlers: function() {
932
            this.name.bind("keypress", _.bind(function(e) {
933
                this.name_changed = true;
934
                if (e.keyCode == 13) { this.parent.set_step(4); this.parent.update_layout() };    
935
            }, this));
936

    
937
            this.name.bind("click", _.bind(function() {
938
                if (!this.name_changed) {
939
                    this.name.val("");
940
                }
941
            }, this))
942
        },
943

    
944
        show: function() {
945
            views.CreatePersonalizeView.__super__.show.apply(this, arguments);
946
            this.update_layout();
947
        },
948
        
949
        update_layout: function() {
950
            var params = this.parent.get_params();
951

    
952
            if (!params.image || !params.flavor) { return }
953

    
954
            if (!params.image) { return }
955
            var vm_name_tpl = snf.config.vm_name_template || "My {0} server";
956
            var vm_name = vm_name_tpl.format(_.escape(params.image.get("name")));
957
            var orig_name = vm_name;
958
            
959
            var existing = true;
960
            var j = 0;
961

    
962
            while (existing && !this.name_changed) {
963
                var existing = storage.vms.select(function(vm){return vm.get("name") == vm_name}).length
964
                if (existing) {
965
                    j++;
966
                    vm_name = orig_name + " " + j;
967
                }
968
            }
969

    
970
            if (!_(this.name.val()).trim() || !this.name_changed) {
971
                this.name.val(vm_name);
972
            }
973

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

    
994
            this.update_ui_keys_selections(this.selected_keys);
995
        },
996

    
997
        disable_ssh_keys: function() {
998
            this.$(".disabled.desc").show();
999
            this.$(".empty.desc").hide();
1000
            this.$(".ssh .confirm-params").hide();
1001
            this.selected_keys = [];
1002
        },
1003

    
1004
        enable_ssh_keys: function() {
1005
            this.$(".ssh .confirm-params").show();
1006
            this.$(".disabled.desc").hide();
1007
        },
1008

    
1009
        reset: function() {
1010
            this.roles.find(".val").removeClass("selected");
1011
            this.name_changed = false;
1012
            this.selected_keys = [];
1013
            this.update_layout();
1014
        },
1015

    
1016
        get_meta: function() {
1017
            if (this.roles.find(".selected").length == 0) {
1018
                return false;
1019
            }
1020

    
1021
            var role = $(this.roles.find(".selected").get(0)).data("value");
1022
            return {'Role': role }
1023
        },
1024

    
1025
        get: function() {
1026
            var val = {'name': this.name.val() };
1027
            if (this.get_meta()) {
1028
                val.metadata = this.get_meta();
1029
            }
1030

    
1031
            val.keys = _.map(this.selected_keys, function(k){ return snf.storage.keys.get(k)});
1032
            
1033
            return val;
1034
        }
1035
    });
1036

    
1037
    views.CreateSubmitView = views.CreateVMStepView.extend({
1038
        step: 4,
1039
        initialize: function() {
1040
            views.CreateSubmitView.__super__.initialize.apply(this, arguments);
1041
            this.roles = this.$("li.predefined-meta.role .values");
1042
            this.confirm = this.$(".confirm-params ul");
1043
            this.name = this.$("h3.vm-name");
1044
            this.keys = this.$(".confirm-params.ssh");
1045
            this.meta = this.$(".confirm-params.meta");
1046
            this.init_handlers();
1047
        },
1048

    
1049
        init_handlers: function() {
1050
        },
1051

    
1052
        show: function() {
1053
            views.CreateSubmitView.__super__.show.apply(this, arguments);
1054
            this.update_layout();
1055
        },
1056
        
1057
        update_flavor_details: function() {
1058
            var flavor = this.parent.get_params().flavor;
1059

    
1060
            function set_detail(sel, key) {
1061
                var val = key;
1062
                if (key == undefined) { val = flavor.get(sel) };
1063
                this.$(".confirm-cont.flavor .flavor-" + sel + " .value").text(val)
1064
            }
1065
            
1066
            set_detail("cpu", flavor.get("cpu") + "x");
1067
            set_detail("ram", flavor.get("ram") + " MB");
1068
            set_detail("disk", util.readablizeBytes(flavor.get("disk") * 1024 * 1024 * 1024));
1069
            set_detail("disktype", flavor.get_disk_template_info().name);
1070
        },
1071

    
1072
        update_image_details: function() {
1073
            var image = this.parent.get_params().image;
1074

    
1075
            function set_detail(sel, key) {
1076
                var val = key;
1077
                if (key == undefined) { val = image.get(sel) };
1078
                this.$(".confirm-cont.image .image-" + sel + " .value").text(val)
1079
            }
1080
            
1081
            set_detail("description");
1082
            set_detail("name");
1083
            set_detail("os", _(image.escape("OS")).capitalize());
1084
            set_detail("gui", image.get("GUI"));
1085
            set_detail("size", _.escape(image.get_readable_size()));
1086
            set_detail("kernel");
1087
        },
1088

    
1089
        update_selected_keys: function(keys) {
1090
            this.keys.empty();
1091
            if (!keys || keys.length == 0) {
1092
                this.keys.append(this.make("li", {'class':'empty'}, 'No keys selected'))
1093
            }
1094
            _.each(keys, _.bind(function(key) {
1095
                var el = this.make("li", {'class':'selected-ssh-key'}, key.get('name'));
1096
                this.keys.append(el);
1097
            }, this))
1098
        },
1099

    
1100
        update_selected_meta: function(meta) {
1101
            this.meta.empty();
1102
            if (!meta || meta.length == 0) {
1103
                this.meta.append(this.make("li", {'class':'empty'}, 'No tags selected'))
1104
            }
1105
            _.each(meta, _.bind(function(value, key) {
1106
                var el = this.make("li", {'class':"confirm-value"});
1107
                var name = this.make("span", {'class':"ckey"}, key);
1108
                var value = this.make("span", {'class':"cval"}, value);
1109

    
1110
                $(el).append(name)
1111
                $(el).append(value);
1112
                this.meta.append(el);
1113
            }, this));
1114
        },
1115

    
1116
        update_layout: function() {
1117
            var params = this.parent.get_params();
1118
            if (!params.image || !params.flavor) { return }
1119

    
1120
            if (!params.image) { return }
1121

    
1122
            this.name.text(params.name);
1123

    
1124
            this.confirm.find("li.image .value").text(params.flavor.get("image"));
1125
            this.confirm.find("li.cpu .value").text(params.flavor.get("cpu") + "x");
1126
            this.confirm.find("li.mem .value").text(params.flavor.get("ram"));
1127
            this.confirm.find("li.disk .value").text(params.flavor.get("disk"));
1128

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

    
1132
            this.update_image_details();
1133
            this.update_flavor_details();
1134

    
1135
            if (!params.image.supports('ssh')) {
1136
                this.keys.hide();
1137
                this.keys.prev().hide();
1138
            } else {
1139
                this.keys.show();
1140
                this.keys.prev().show();
1141
                this.update_selected_keys(params.keys);
1142
            }
1143
            
1144
            this.update_selected_meta(params.metadata);
1145
        },
1146

    
1147
        reset: function() {
1148
            this.update_layout();
1149
        },
1150

    
1151
        get_meta: function() {
1152
        },
1153

    
1154
        get: function() {
1155
            return {};
1156
        }
1157
    });
1158

    
1159
    views.CreateVMView = views.Overlay.extend({
1160
        
1161
        view_id: "create_vm_view",
1162
        content_selector: "#createvm-overlay-content",
1163
        css_class: 'overlay-createvm overlay-info',
1164
        overlay_id: "metadata-overlay",
1165

    
1166
        subtitle: false,
1167
        title: "Create new machine",
1168

    
1169
        initialize: function(options) {
1170
            views.CreateVMView.__super__.initialize.apply(this);
1171
            this.current_step = 1;
1172

    
1173
            this.password_view = new views.VMCreationPasswordView();
1174

    
1175
            this.steps = [];
1176
            this.steps[1] = new views.CreateImageSelectView(this);
1177
            this.steps[1].bind("change", _.bind(function(data) {this.trigger("image:change", data)}, this));
1178

    
1179
            this.steps[2] = new views.CreateFlavorSelectView(this);
1180
            this.steps[3] = new views.CreatePersonalizeView(this);
1181
            this.steps[4] = new views.CreateSubmitView(this);
1182

    
1183
            this.cancel_btn = this.$(".create-controls .cancel");
1184
            this.next_btn = this.$(".create-controls .next");
1185
            this.prev_btn = this.$(".create-controls .prev");
1186
            this.submit_btn = this.$(".create-controls .submit");
1187

    
1188
            this.history = this.$(".steps-history");
1189
            this.history_steps = this.$(".steps-history .steps-history-step");
1190
            
1191
            this.init_handlers();
1192
        },
1193

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

    
1218
        set_step: function(st) {
1219
        },
1220
        
1221
        validate: function(data) {
1222
            if (_(data.name).trim() == "") {
1223
                this.$(".form-field").addClass("error");
1224
                return false;
1225
            } else {
1226
                return true;
1227
            }
1228
        },
1229

    
1230
        submit: function() {
1231
            if (this.submiting) { return };
1232
            var data = this.get_params();
1233
            var meta = {};
1234
            var extra = {};
1235
            var personality = [];
1236

    
1237
            if (this.validate(data)) {
1238
                this.submit_btn.addClass("in-progress");
1239
                this.submiting = true;
1240
                if (data.metadata) { meta = data.metadata; }
1241
                if (data.keys && data.keys.length > 0) {
1242
                    personality.push(data.image.personality_data_for_keys(data.keys))
1243
                }
1244

    
1245
                if (personality.length) {
1246
                    extra['personality'] = personality;
1247
                }
1248

    
1249
                storage.vms.create(data.name, data.image, data.flavor, meta, extra, _.bind(function(data){
1250
                    this.close_all();
1251
                    this.password_view.show(data.server.adminPass, data.server.id);
1252
                    this.submiting = false;
1253
                }, this));
1254
            }
1255
        },
1256

    
1257
        close_all: function() {
1258
            this.hide();
1259
        },
1260

    
1261
        reset: function() {
1262
            this.current_step = 1;
1263

    
1264
            this.steps[1].reset();
1265
            this.steps[2].reset();
1266
            this.steps[3].reset();
1267
            this.steps[4].reset();
1268

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

    
1274
            this.submit_btn.removeClass("in-progress");
1275
        },
1276

    
1277
        onShow: function() {
1278
        },
1279

    
1280
        update_layout: function() {
1281
            this.show_step(this.current_step);
1282
            this.current_view.update_layout();
1283
        },
1284

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

    
1308
        show_step: function(step) {
1309
            // FIXME: this shouldn't be here
1310
            // but since we are not calling step.hide this should work
1311
            this.steps[1].image_details.hide();
1312

    
1313
            this.current_view = this.steps[step];
1314
            this.update_controls();
1315

    
1316
            this.steps[step].show();
1317
            var width = this.el.find('.container').width();
1318
            var left = (step -1) * width * -1;
1319
            this.$(".steps-container").animate({"margin-left": left + "px"}, 300);
1320

    
1321
            this.update_steps_history();
1322
        },
1323

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

    
1343
        update_controls: function() {
1344
            var step = this.current_step;
1345
            if (step == 1) {
1346
                this.prev_btn.hide();
1347
                this.cancel_btn.show();
1348
            } else {
1349
                this.prev_btn.show();
1350
                this.cancel_btn.hide();
1351
            }
1352
            
1353
            if (step == this.steps.length - 1) {
1354
                this.next_btn.hide();
1355
                this.submit_btn.show();
1356
            } else {
1357
                this.next_btn.show();
1358
                this.submit_btn.hide();
1359
            }
1360
        },
1361

    
1362
        get_params: function() {
1363
            return _.extend({}, this.steps[1].get(), this.steps[2].get(), this.steps[3].get());
1364
        }
1365
    });
1366
    
1367
})(this);
1368