Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (67.8 kB)

1
// Copyright 2011 GRNET S.A. All rights reserved.
2
// 
3
// Redistribution and use in source and binary forms, with or
4
// without modification, are permitted provided that the following
5
// conditions are met:
6
// 
7
//   1. Redistributions of source code must retain the above
8
//      copyright notice, this list of conditions and the following
9
//      disclaimer.
10
// 
11
//   2. Redistributions in binary form must reproduce the above
12
//      copyright notice, this list of conditions and the following
13
//      disclaimer in the documentation and/or other materials
14
//      provided with the distribution.
15
// 
16
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
// POSSIBILITY OF SUCH DAMAGE.
28
// 
29
// The views and conclusions contained in the software and
30
// documentation are those of the authors and should not be
31
// interpreted as representing official policies, either expressed
32
// or implied, of GRNET S.A.
33
// 
34

    
35
;(function(root){
36

    
37
    // root
38
    var root = root;
39
    
40
    // setup namepsaces
41
    var snf = root.synnefo = root.synnefo || {};
42
    var models = snf.models = snf.models || {}
43
    var storage = snf.storage = snf.storage || {};
44
    var ui = snf.ui = snf.ui || {};
45
    var util = snf.util = snf.util || {};
46

    
47
    var views = snf.views = snf.views || {}
48

    
49
    // shortcuts
50
    var bb = root.Backbone;
51

    
52

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

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

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

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

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

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

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

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

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

    
117

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

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

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

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

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

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

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

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

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

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

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

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

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

    
186
            // handlers initialization
187
            this.create_types_selection_options();
188
            this.create_snapshot_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
            $(".image-warning .confirm").bind('click', function(){
217
                $(".image-warning").hide();
218
                $(".create-controls").show();
219
            })
220
        },
221

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

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

    
236
        create_snapshot_types_selection_options: function() {
237
            var exclude = [];
238
            var list = this.$(".snapshot-types-cont ul.type-filter");
239
            _.each(this.type_selections_order, _.bind(function(key) {
240
                if (_.includes(exclude, key)) { return }
241
                var label = this.type_selections[key].replace("images", "snapshots");
242
                list.append('<li id="type-select-snapshot-{0}">{1}</li>'.format(key, label));
243
            }, this));
244
            this.types = this.$(".type-filter li");
245
        },
246

    
247
        update_layout: function() {
248
            if (!this.selected_type) {
249
                this.selected_type = _.keys(this.type_selections)[0];
250
            }
251
            this.select_type(this.selected_type);
252
        },
253
        
254
        get_categories: function(images) {
255
            return [];
256
            return ["Desktop", "Server", "Linux", "Windows"];
257
        },
258

    
259
        reset_categories: function() {
260
            var categories = this.get_categories(this.images);
261
            this.categories_list.find("li").remove();
262

    
263
            _.each(categories, _.bind(function(cat) {
264
                var el = $("<li />");
265
                el.text(cat);
266
                this.categories_list.append(el);
267
            }, this));
268

    
269
            if (!categories.length) { 
270
                this.categories_list.parent().find(".clear").hide();
271
                this.categories_list.parent().find(".empty").show();
272
            } else {
273
                this.categories_list.parent().find(".clear").show();
274
                this.categories_list.parent().find(".empty").hide();
275
            }
276
        },
277
        
278
        show_loading_view: function() {
279
            this.$(".images-list-cont .empty").hide();
280
            this.images_list.hide();
281
            this.$(".images-list-cont .loading").show();
282
            this.$(".images-list-cont .images-list").hide();
283
            this.reset_categories();
284
            this.update_images([]);
285
            this.reset_images();
286
            this.hide_list_loading();
287
        },
288

    
289
        hide_loading_view: function(images) {
290
            this.$(".images-list-cont .loading").hide();
291
            this.$(".images-list-cont .images-list").show();
292
            this.reset_categories();
293
            this.update_images(images);
294
            this.reset_images();
295
            this.select_image(this.selected_image);
296
            this.hide_list_loading();
297
            $(".custom-image-help").hide();
298
            if (this.selected_type == 'personal' && !images.length) {
299
                $(".custom-image-help").show();
300
            }
301

    
302
        },
303

    
304
        select_type: function(type) {
305
            this.selected_type = type;
306
            this.types.removeClass("selected");
307
            var selection = "#type-select-" + this.selected_type;
308
            this.types.filter("#type-select-" + this.selected_type).addClass("selected");
309
            this.images_storage.update_images_for_type(
310
                this.selected_type, 
311
                _.bind(this.show_loading_view, this), 
312
                _.bind(this.hide_loading_view, this)
313
            );
314

    
315
            this.update_layout_for_type(type);
316
        },
317

    
318
        update_layout_for_type: function(type) {
319
            if (type != "system") {
320
                this.$(".custom-action").hide();
321
            } else {
322
                this.$(".custom-action").hide();
323
            }
324

    
325
        },
326

    
327
        show_list_loading: function() {
328
            this.$(".images-list-cont").addClass("loading");
329
        },
330

    
331
        hide_list_loading: function() {
332
            this.$(".images-list-cont").removeClass("loading");
333
        },
334
        
335
        display_warning_for_image: function(image) {
336
          if (image && !image.is_system_image() && !image.owned_by(synnefo.user)) {
337
            $(".create-vm .image-warning").show();
338
            $(".create-controls").hide();
339
          } else {
340
            $(".create-vm .image-warning").hide();
341
            $(".create-controls").show();
342
          }
343
        },
344

    
345
        select_image: function(image) {
346
            if (image && image.get('id') && !_.include(this.images_ids, image.get('id'))) {
347
                image = undefined;
348
            }
349
            if (!image && this.images_ids.length) {
350
                if (this.selected_image && this.images_ids.indexOf(this.selected_image.id) > -1) {
351
                    image = this.selected_image;
352
                } else {
353
                    image = this.images_storage.get(this.images_ids[0]);
354
                }
355
            }
356
             
357
            // no images select null image so that next button gets hidden
358
            if (!this.images_ids.length) { image = undefined };
359
            
360
            if ((!this.selected_image && image) || (this.selected_image != image))
361
                this.trigger("change", image);
362
                this.display_warning_for_image(image);
363

    
364
            this.selected_image = image;
365
                
366
            if (image) {
367
                this.images_list.find(".image-details").removeClass("selected");
368
                this.images_list.find(".image-details#create-vm-image-" + this.selected_image.id).addClass("selected");
369
                this.update_image_details(image);
370

    
371
            } else {
372
            }
373

    
374
            this.image_details.hide();
375
            this.validate();
376
        },
377

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

    
413
                    if (!value) {
414
                        value = image.get_meta(key);
415
                    }
416
                }
417
                    
418
                if (!value) { return; }
419
                 
420
                var label = this.images_storage.meta_labels[key];
421
                if (!label) {
422
                    var label = _(key.replace(/_/g," ")).capitalize();
423
                }
424
                var row_cls = key.toLowerCase();
425
                if (is_extra) { row_cls += " extra-meta" };
426
                extra_details.append(detail_tpl.format(_.escape(label), value, row_cls));
427
            }
428

    
429
            _.each(meta_keys, function(key) {
430
                append_metadata_row.apply(this, [key]);
431
            }, this);
432
            
433
            if (synnefo.storage.images.display_extra_metadata) {
434
                _.each(image.get('metadata'), function(value, key) {
435
                    if (!_.contains(meta_keys, key) && 
436
                        !_.contains(meta_keys, key.toLowerCase()) &&
437
                        !_.contains(meta_keys, key.toUpperCase()) &&
438
                        !_.contains(skip_keys, key)) {
439
                            append_metadata_row.apply(this, [key, true]);
440
                    }
441
                }, this);
442
            }
443
        },
444

    
445
        reset_images: function() {
446
            this.images_list.find("li").remove();
447
            _.each(this.images, _.bind(function(img){
448
                this.add_image(img);
449
            }, this))
450
            
451
            if (this.images.length) {
452
                this.images_list.parent().find(".empty").hide();
453
                this.images_list.show();
454
            } else {
455
                this.images_list.parent().find(".empty").show();
456
                this.images_list.hide();
457
            }
458

    
459
            var self = this;
460
            this.images_list.find(".image-details").click(function(){
461
                self.select_image($(this).data("image"));
462
            });
463
            
464
        },
465

    
466
        show: function() {
467
            this.image_details.hide();
468
            this.parent.$(".create-controls").show();
469

    
470
            views.CreateImageSelectView.__super__.show.apply(this, arguments);
471
        },
472

    
473
        add_image: function(img) {
474
            var image = $(('<li id="create-vm-image-{1}"' +
475
                           'class="image-details clearfix">{2}{0}'+
476
                           '<span class="show-details">details</span>'+
477
                           '<span class="size"><span class="prepend">by </span>{5}</span>' + 
478
                           '<span class="owner">' +
479
                           '<span class="prepend"></span>' +
480
                           '{3}</span>' + 
481
                           '<p>{4}</p>' +
482
                           '</li>').format(_.escape(util.truncate(img.get("name"), 37)), 
483
                                                  img.id, 
484
                                                  snf.ui.helpers.os_icon_tag(img.escape("OS")),
485
                                                  _.escape(img.get_readable_size()),
486
                                                  util.truncate(img.get_description(false), 35),
487
                                                  _.escape(img.display_owner())));
488
            image.data("image", img);
489
            image.data("image_id", img.id);
490
            this.images_list.append(image);
491
            image.find(".show-details").click(_.bind(function(e){
492
                e.preventDefault();
493
                e.stopPropagation();
494
                this.show_image_details(img);
495
            }, this))
496
        },
497
            
498
        hide_image_details: function() {
499
            this.image_details.fadeOut(200);
500
            this.parent.$(".create-controls").show();
501
        },
502

    
503
        show_image_details: function(img) {
504
            this.parent.$(".create-controls").hide();
505
            this.update_image_details(img);
506
            this.image_details.fadeIn(100);
507
        },
508

    
509
        reset: function() {
510
            this.selected_image = false;
511
            this.select_type("system");
512
        },
513

    
514
        get: function() {
515
            return {'image': this.selected_image};
516
        },
517

    
518
        validate: function() {
519
            if (!this.selected_image) {
520
                this.parent.$(".form-action.next").hide();
521
            } else {
522
                this.parent.$(".form-action.next").show();
523
            }
524
        }
525
    });
526

    
527
    views.CreateFlavorSelectView = views.CreateVMStepView.extend({
528
        step: 2,
529
        initialize: function() {
530
            views.CreateFlavorSelectView.__super__.initialize.apply(this, arguments);
531
            this.parent.bind("image:change", _.bind(this.handle_image_change, this));
532

    
533
            this.cpus = this.$(".flavors-cpu-list");
534
            this.disks = this.$(".flavors-disk-list");
535
            this.disk_templates = this.$(".flavors-disk-template-list");
536
            this.mems = this.$(".flavors-mem-list");
537

    
538
            this.predefined_flavors = SUGGESTED_FLAVORS;
539
            this.predefined_flavors_keys = _.keys(SUGGESTED_FLAVORS);
540
            this.predefined_flavors_keys = _.sortBy(this.predefined_flavors_keys, _.bind(function(k){
541
                var flv = this.predefined_flavors[k];
542
                return (flv.ram * flv.cpu * flv.disk);
543
            }, this));
544

    
545
            this.predefined = this.$(".predefined-list");
546
        },
547

    
548
        handle_image_change: function(data) {
549
            this.current_image = data;
550
            this.update_valid_predefined();
551
            this.current_flavor = undefined;
552
            this.update_flavors_data();
553
            this.update_predefined_flavors();
554
            this.reset_flavors();
555
            this.update_layout();
556
        },
557

    
558
        validate_selected_flavor: function() {
559
            if (!this.flavor_is_valid(this.current_flavor)) {
560
                this.select_valid_flavor();
561
            }
562
        },
563

    
564
        reset_flavors: function() {
565
            this.$(".flavor-opts-list .option").remove();
566
            this.create_flavors();
567
        },
568

    
569
        update_predefined_flavors: function() {
570
            this.predefined.find("li").remove();
571
            _.each(this.predefined_flavors_keys, _.bind(function(key) {
572
                var val = this.predefined_flavors[key];
573
                var el = $(('<li class="predefined-selection" id="predefined-flavor-{0}">' +
574
                           '{1}</li>').format(key, _.escape(_(key).capitalize())));
575

    
576
                this.predefined.append(el);
577
                el.data({flavor: storage.flavors.get_flavor(val.cpu, val.ram, val.disk, val.disk_template, this.flavors)});
578
                el.click(_.bind(function() {
579
                    this.handle_predefined_click(el);
580
                }, this))
581
            }, this));
582
            this.update_valid_predefined();
583
        },
584

    
585
        handle_predefined_click: function(el) {
586
            if (el.hasClass("disabled")) { return };
587
            this.set_current(el.data("flavor"));
588
        },
589

    
590
        select_valid_flavor: function() {
591
            var found = false;
592
            var self = this;
593

    
594
            _.each(["cpu", "mem", "disk"], function(t) {
595
              var el = $(".flavor-options."+t);
596
              var all = el.find(".flavor-opts-list li").length;
597
              var disabled = el.find(".flavor-opts-list li.disabled").length;
598
              if (disabled >= all) {
599
                el.find("h4").addClass("error");
600
              } else {
601
                el.find("h4").removeClass("error");
602
              }
603
            })
604

    
605
            _.each(this.flavors, function(flv) {
606
                if (self.flavor_is_valid(flv)) {
607
                    found = flv;
608
                    return false;
609
                }
610
            });
611
            
612
            if (found) {
613
                this.set_current(found);
614
            } else {
615
                this.current_flavor = undefined;
616
                this.validate();
617
                this.$("li.predefined-selection").addClass("disabled");
618
                this.$(".flavor-opts-list li").removeClass("selected");
619
            }
620
        },
621

    
622
        update_valid_predefined: function() {
623
            this.update_unavailable_values();
624
            var self = this;
625
            this.valid_predefined = _.select(
626
              _.map(this.predefined_flavors, function(flv, key){
627
                var existing = storage.flavors.get_flavor(flv.cpu, 
628
                                                          flv.ram, 
629
                                                          flv.disk, 
630
                                                          flv.disk_template, 
631
                                                          self.flavors);
632
                // non existing
633
                if (!existing) {
634
                    return false;
635
                }
636
                
637
                // not available for image
638
                if (self.unavailable_values && self.unavailable_values.disk.indexOf(
639
                    existing.get("disk")) > -1) {
640
                      return false
641
                }
642
                
643
                // quota check
644
                var quotas = synnefo.storage.quotas.get_available_for_vm();
645
                var unavailable_check = 
646
                  synnefo.storage.flavors.unavailable_values_for_quotas;
647
                var unavailable = unavailable_check(quotas, [existing]);
648
                if ((_.filter(unavailable, function(values, flvkey) {
649
                  return values.length > 0
650
                })).length > 0) {
651
                  return false;
652
                }
653
                
654
                return key;
655
            }), function(ret) { return ret });
656
            
657
            $("li.predefined-selection").addClass("disabled");
658
            _.each(this.valid_predefined, function(key) {
659
                $("#predefined-flavor-" + key).removeClass("disabled");
660
            })
661
        },
662

    
663
        update_selected_predefined: function() {
664
            var self = this;
665
            this.predefined.find("li").removeClass("selected");
666

    
667
            _.each(this.valid_predefined, function(key){
668
                var flv = self.predefined_flavors[key];
669
                var exists = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, flv.disk_template, self.flavors);
670

    
671
                if (exists && (exists.id == self.current_flavor.id)) {
672
                    $("#predefined-flavor-" + key).addClass("selected");
673
                }
674
            })
675
        },
676
        
677
        update_flavors_data: function() {
678
            this.flavors = storage.flavors.active();
679
            this.flavors_data = storage.flavors.get_data(this.flavors);
680
            
681
            var self = this;
682
            var set = false;
683
            
684
            // FIXME: validate current flavor
685
            
686
            if (!this.current_flavor) {
687
                _.each(this.valid_predefined, function(key) {
688
                    var flv = self.predefined_flavors[key];
689
                    var exists = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, flv.disk_template, self.flavors);
690
                    if (exists && !set) {
691
                        self.set_current(exists);
692
                        set = true;
693
                    }
694
                })
695
            }
696

    
697
            this.update_unavailable_values();
698
        },
699

    
700
        update_unavailable_values: function() {
701
            
702
            var unavailable = {disk:[], ram:[], cpu:[]}
703
            var user_excluded = {disk:[], ram:[], cpu:[]}
704
            var image_excluded = {disk:[], ram:[], cpu:[]}
705

    
706
            if (this.current_image) {
707
              image_excluded = storage.flavors.unavailable_values_for_image(this.current_image);
708
            }
709

    
710
            var quotas = synnefo.storage.quotas.get_available_for_vm({active: true});
711
            var user_excluded = storage.flavors.unavailable_values_for_quotas(quotas);
712

    
713
            unavailable.disk = user_excluded.disk.concat(image_excluded.disk);
714
            unavailable.ram = user_excluded.ram.concat(image_excluded.ram);
715
            unavailable.cpu = user_excluded.cpu.concat(image_excluded.cpu);
716
            
717
            this.unavailable_values = unavailable;
718
        },
719
        
720
        flavor_is_valid: function(flv) {
721
            if (!flv) { return false };
722

    
723
            var existing = storage.flavors.get_flavor(flv.get("cpu"), flv.get("ram"), flv.get("disk"), flv.get("disk_template"), this.flavors);
724
            if (!existing) { return false };
725
            
726
            if (this.unavailable_values && (this.unavailable_values.disk.indexOf(parseInt(flv.get("disk"))) > -1)) {
727
                return false;
728
            }
729
            if (this.unavailable_values && (this.unavailable_values.ram.indexOf(parseInt(flv.get("ram"))) > -1)) {
730
                return false;
731
            }
732
            if (this.unavailable_values && (this.unavailable_values.cpu.indexOf(parseInt(flv.get("cpu"))) > -1)) {
733
                return false;
734
            }
735
            return true;
736
        },
737
            
738
        set_valid_current_for: function(t, val) {
739
            var found = this.flavors[0];
740
            _.each(this.flavors, function(flv) {
741
                if (flv.get(t) == val) {
742
                    found = flv;
743
                }
744
            });
745

    
746
            this.set_current(found);
747
            this.validate_selected_flavor();
748
        },
749

    
750
        set_current: function(flv) {
751

    
752
            if (!flv) {
753
                // user clicked on invalid combination
754
                // force the first available choice for the
755
                // type of option he last clicked
756
                this.set_valid_current_for.apply(this, this.last_choice);
757
                return;
758
            }
759

    
760
            this.current_flavor = flv;
761
            this.trigger("change");
762
            if (this.current_flavor) {
763
                this.update_selected_flavor();
764
                this.update_selected_predefined();
765
            }
766
            
767
            this.validate();
768
        },
769
        
770
        select_default_flavor: function() {
771
               
772
        },
773

    
774
        update_selected_from_ui: function() {
775
            this.set_current(this.ui_selected());
776
        },
777
        
778
        update_disabled_flavors: function() {
779
            this.$(".flavor-options.disk li").removeClass("disabled");
780
            if (!this.unavailable_values) { return }
781
            
782
            this.$("#create-vm-flavor-options .flavor-options.disk li").each(_.bind(function(i, el){
783
                var el_value = $(el).data("value");
784
                if (this.unavailable_values.disk.indexOf(el_value) > -1) {
785
                    $(el).addClass("disabled");
786
                    $(el).removeClass("selected");
787
                };
788
            }, this));
789

    
790
            this.$("#create-vm-flavor-options .flavor-options.mem li").each(_.bind(function(i, el){
791
                var el_value = $(el).data("value");
792
                if (this.unavailable_values.ram.indexOf(el_value) > -1) {
793
                    $(el).addClass("disabled");
794
                    $(el).removeClass("selected");
795
                };
796
            }, this));
797

    
798
            this.$("#create-vm-flavor-options .flavor-options.cpu li").each(_.bind(function(i, el){
799
                var el_value = $(el).data("value");
800
                if (this.unavailable_values.cpu.indexOf(el_value) > -1) {
801
                    $(el).addClass("disabled");
802
                    $(el).removeClass("selected");
803
                };
804
            }, this));
805
        },
806

    
807
        create_flavors: function() {
808
            var flavors = this.get_active_flavors();
809
            var valid_flavors = this.get_valid_flavors();
810
            this.__added_flavors = {'cpu':[], 'ram':[], 'disk':[], 'disk_template':[] };
811

    
812
            _.each(flavors, _.bind(function(flv){
813
                this.add_flavor(flv);
814
            }, this));
815
            
816
            this.sort_flavors(this.disks);
817
            this.sort_flavors(this.cpus);
818
            this.sort_flavors(this.mems);
819
            this.sort_flavors(this.disk_templates);
820

    
821
            var self = this;
822
            this.$(".flavor-options li.option").click(function(){
823
                var el = $(this);
824

    
825
                if (el.hasClass("disabled")) { return }
826

    
827
                el.parent().find(".option").removeClass("selected");
828
                el.addClass("selected");
829

    
830
                if (el.hasClass("mem")) { self.last_choice = ["ram", $(this).data("value")] }
831
                if (el.hasClass("cpu")) { self.last_choice = ["cpu", $(this).data("value")] }
832
                if (el.hasClass("disk")) { self.last_choice = ["disk", $(this).data("value")] }
833
                if (el.hasClass("disk_template")) { self.last_choice = ["disk_template", $(this).data("value")] }
834

    
835
                self.update_selected_from_ui();
836
            });
837

    
838
            $(".flavor-opts-list").each(function(){
839
              var el = $(this);
840
              if (el.find(".option").length > 6) {
841
                el.addClass("compact");
842
              }
843
            });
844
        },
845

    
846
        sort_flavors: function(els) {
847
            var prev = undefined;
848
            els.find("li").each(function(i,el){
849
                el = $(el);
850
                if (!prev) { prev = el; return true };
851
                if (el.data("value") < prev.data("value")) {
852
                    prev.before(el);
853
                }
854
                prev = el;
855
            })
856
        },
857
        
858
        ui_selected: function() {
859
            var args = [this.$(".option.cpu.selected").data("value"), 
860
                this.$(".option.mem.selected").data("value"), 
861
                this.$(".option.disk.selected").data("value"),
862
                this.$(".option.disk_template.selected").data("value"),
863
            this.flavors];
864
            
865
            var flv = storage.flavors.get_flavor.apply(storage.flavors, args);
866
            return flv;
867
        },
868

    
869
        update_selected_flavor: function() {
870
            var flv = this.current_flavor;
871
            if (!flv) { return }
872
            this.$(".option").removeClass("selected");
873

    
874
            this.$(".option.cpu.value-" + flv.get("cpu")).addClass("selected");
875
            this.$(".option.mem.value-" + flv.get("ram")).addClass("selected");
876
            this.$(".option.disk.value-" + flv.get("disk")).addClass("selected");
877
            this.$(".option.disk_template.value-" + flv.get("disk_template")).addClass("selected");
878
            
879
            var disk_el = this.$(".option.disk_template.value-" + flv.get("disk_template"));
880
            var basebgpos = 470;
881
                
882
            var append_to_bg_pos = 40 + (disk_el.index() * 91);
883
            var bg_pos = basebgpos - append_to_bg_pos;
884

    
885
            this.$(".disk-template-description").css({backgroundPosition:'-' + bg_pos + 'px top'})
886
            this.$(".disk-template-description p").html(flv.get_disk_template_info().description || "");
887
        },
888
        
889
        __added_flavors: {'cpu':[], 'ram':[], 'disk':[], 'disk_template':[]},
890
        add_flavor: function(flv) {
891
            var values = {'cpu': flv.get('cpu'), 
892
                          'mem': flv.get('ram'), 
893
                          'disk': flv.get('disk'), 
894
                          'disk_template': flv.get('disk_template')};
895

    
896
            disabled = "";
897
            
898
            if (this.__added_flavors.cpu.indexOf(values.cpu) == -1) {
899
                var cpu = $(('<li class="option cpu value-{0} {1}">' + 
900
                             '<span class="value">{0}</span>' + 
901
                             '<span class="metric">x</span></li>').format(
902
                            _.escape(values.cpu), disabled)).data('value', values.cpu);
903
                this.cpus.append(cpu);
904
                this.__added_flavors.cpu.push(values.cpu);
905
            }
906

    
907
            if (this.__added_flavors.ram.indexOf(values.mem) == -1) {
908
                var mem_value = parseInt(_.escape(values.mem))*1024*1024;
909
                var displayvalue = synnefo.util.readablizeBytes(mem_value, 
910
                                                               0).split(" ");
911
                var mem = $(('<li class="option mem value-{2}">' + 
912
                             '<span class="value">{0}</span>' + 
913
                             '<span class="metric">{1}</span></li>').format(
914
                          displayvalue[0], displayvalue[1], values.mem)).data(
915
                          'value', values.mem);
916
                this.mems.append(mem);
917
                this.__added_flavors.ram.push(values.mem);
918
            }
919

    
920
            if (this.__added_flavors.disk.indexOf(values.disk) == -1) {
921
                var disk = $(('<li class="option disk value-{0}">' + 
922
                              '<span class="value">{0}</span>' + 
923
                              '<span class="metric">GB</span></li>').format(
924
                            _.escape(values.disk))).data('value', values.disk);
925
                this.disks.append(disk);
926
                this.__added_flavors.disk.push(values.disk)
927
            }
928
            
929
            if (this.__added_flavors.disk_template.indexOf(values.disk_template) == -1) {
930
                var template_info = flv.get_disk_template_info();
931
                var disk_template = $(('<li title="{2}" class="option disk_template value-{0}">' + 
932
                                       '<span class="value name">{1}</span>' +
933
                                       '</li>').format(values.disk_template, 
934
                                            _.escape(template_info.name), 
935
                                            template_info.description)).data('value', 
936
                                                                values.disk_template);
937

    
938
                this.disk_templates.append(disk_template);
939
                //disk_template.tooltip({position:'top center', offset:[-5,0], delay:100, tipClass:'tooltip disktip'});
940
                this.__added_flavors.disk_template.push(values.disk_template)
941
            }
942
            
943
        },
944
        
945
        get_active_flavors: function() {
946
            return storage.flavors.active();
947
        },
948

    
949
        get_valid_flavors: function() {
950
            return this.flavors;
951
        },
952

    
953
        update_layout: function() {
954
            this.update_selected_flavor();
955
            this.update_disabled_flavors();
956
            this.validate();
957
            this.validate_selected_flavor();
958
            this.update_quota_display();
959
        },
960
        
961
        update_quota_display: function() {
962

    
963
          var quotas = synnefo.storage.quotas;
964
          _.each(["disk", "ram", "cpu"], function(type) {
965
            var active = true;
966
            var key = 'available';
967
            var available_dsp = quotas.get('cyclades.'+type).get_readable(key, active);
968
            var available = quotas.get('cyclades.'+type).get_available(key);
969
            var content = "({0} left)".format(available_dsp);
970
            if (available <= 0) { content = "(None left)" }
971
            
972
            if (type == "ram") { type = "mem" }
973
            $(".flavor-options."+type+" h4 .available").text(content);
974
            if (available <= 0) {
975
              $(".flavor-options."+type+" h4 .available").addClass("error");
976
            } else {
977
              $(".flavor-options."+type+" h4 .available").removeClass("error");
978
            }
979
          })
980
        },
981

    
982
        reset: function() {
983
            this.current_image = storage.images.at(0);
984
            this.flavors = [];
985
            this.flavors_data = {'cpu':[], 'mem':[], 'disk':[]};
986
            this.update_flavors_data();
987
        },
988

    
989
        validate: function() {
990
            if (!this.current_flavor) {
991
                this.parent.$(".form-action.next").hide();
992
            } else {
993
                this.parent.$(".form-action.next").show();
994
            }
995
        },
996

    
997
        get: function() {
998
            return {'flavor': this.current_flavor}
999
        }
1000
    });
1001
    
1002

    
1003
    views.CreateColumnSelectOptionView = bb.View.extend({
1004
        tagName: 'li',
1005
        el: undefined,
1006
        model: undefined,
1007
        id_prefix: 'model-',
1008
        tpl: '<input type="checkbox" class="check"/><span class="title"></span>',
1009
        className: 'list-item-option clearfix',
1010
        events: {
1011
          'click': 'handle_click'
1012
        },
1013

    
1014
        initialize: function(options) {
1015
          _.bindAll(this);
1016
          this.model.bind("change", this.render);
1017
          this.model.bind("remove", this.remove);
1018
          this.selected = false;
1019
          if (options.get_model_title) {
1020
            this.get_model_title = _.bind(options.get_model_title, this);
1021
          }
1022
          this.model_title_attr = options.model_title_attr;
1023
          $(this.el).append($(this.tpl));
1024
        },
1025
        
1026
        id: function() {
1027
          return this.id_prefix + this.model && this.model.id || '';
1028
        },
1029
        
1030
        handle_click: function() {
1031
          this.selected = !this.selected;
1032
          this.render();
1033
        },
1034

    
1035
        remove: function() {
1036
          this.model.unbind("change", this.render);
1037
          this.model.unbind("remove", this.remove);
1038
        },
1039
        
1040
        get_model_title: function() {
1041
          return this.model.get(this.model_title_attr || 'id');
1042
        },
1043

    
1044
        render: function() {
1045
          $(this.el).find(".title").text(this.get_model_title());
1046
          $(this.el).toggleClass('selected', this.selected);
1047
          if (this.selected) {
1048
            $(this.el).find("input").attr("checked", true);
1049
          } else {
1050
            $(this.el).find("input").attr("checked", false);
1051
          }
1052
        }
1053
    });
1054
    
1055
    views.CreateColumnIPOptionView = views.CreateColumnSelectOptionView.extend({
1056
      get_model_title: function() {
1057
        return this.model.get('ip');
1058
      }
1059
    });
1060

    
1061
    views.CreateColumnPrivateNetworkOptionView = views.CreateColumnSelectOptionView.extend({
1062
      get_model_title: function() {
1063
        return this.model.get('name');
1064
      }
1065
    });
1066

    
1067
    views.CreateColumnSelectListView = bb.View.extend({
1068
        collection: undefined,
1069
        header: undefined,
1070
        tagName: 'div',
1071
        extra_class: '',
1072
        el: undefined,
1073
        title_tpl: undefined,
1074
        title: 'List view',
1075
        description: 'List view description.',
1076
        empty_msg: 'No entries.',
1077
        item_cls: views.CreateColumnSelectOptionView,
1078
        className: 'list-cont create-column-select personalize-cont',
1079

    
1080
        initialize: function(options) {
1081
          _.bindAll(this);
1082
          if (options.extra_class) {
1083
            $(this.el).addClass(options.extra_class);
1084
          }
1085
          this.update_collection = options.update_collection;
1086
          this.title = options.title || this.title;
1087
          this.titple_tpl = options.title_tpl || this.title_tpl;
1088
          this.description = options.description || this.description;
1089
          this.empty_msg = options.empty_msg || this.empty_msg;
1090
          this.item_cls = options.item_cls || this.item_cls;
1091
          this.select_first_as_default = options.select_first_as_default;
1092
          this.filter_items = options.filter_items;
1093
          this.post_render_entries = options.post_render_entries || function() {};
1094
          this.init_events = options.init_events || function() {};
1095

    
1096
          this.init_events = _.bind(this.init_events, this);
1097
          this.post_render_entries = _.bind(this.post_render_entries, this);
1098

    
1099
          this._ul = $('<ul class="confirm-params">');
1100
          this._title = $("<h4>");
1101
          this._description = $("<p class='desc'>");
1102
          this._empty = $("<p class='empty hidden desc'>");
1103
          this._empty.html(this.empty_msg);
1104
        
1105
          this.item_views = [];
1106

    
1107
          $(this.el).append(this._title);
1108
          $(this.el).append(this._description);
1109
          $(this.el).append(this._empty);
1110
          $(this.el).append(this._ul);
1111

    
1112
          this['$el'] = $(this.el);
1113

    
1114
          if (!this.title_tpl) { this.title_tpl = this.title };
1115

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

    
1166
        render_entries: function() {
1167
          var entries;
1168
          if (this.filter_items) {
1169
            entries = this.collection.filter(this.filter_items);
1170
          } else {
1171
            entries = this.collection.models;
1172
          }
1173
          
1174
          var selected = this.get_selected();
1175
          
1176
          _.each(entries, _.bind(function(model) {
1177
            if (this.item_views[model.id]) {
1178
              this.item_views[model.id].render();
1179
            } else {
1180
              var view = new this.item_cls({model:model});
1181
              if (!selected.length && this.select_first_as_default) { 
1182
                view.selected = true; selected = [1] 
1183
              }
1184
              view.render();
1185
              this.item_views[model.id] = view;
1186
              this._ul.append($(view.el));
1187
            }
1188
          }, this));
1189
          this.check_empty();
1190
          this.post_render_entries();
1191
        },
1192

    
1193
        remove: function() {
1194
          _.each(this.item_views, function(v){
1195
            v.remove();
1196
          });
1197
          if (this.update_collection) { this.fetcher.stop() }
1198
          this.unbind();
1199
          this.collection.unbind("change", this.render_entries);
1200
          this.collection.unbind("reset", this.render_entries);
1201
          this.collection.unbind("add", this.render_entries);
1202
          this.collection.unbind("remove", this.remove_entry);
1203
          views.CreateColumnSelectListView.__super__.remove.apply(this, arguments);
1204
        }
1205
    });
1206

    
1207
    views.CreateNetworkingView = views.CreateVMStepView.extend({
1208
        step: 3,
1209
        initialize: function() {
1210
            views.CreateNetworkingView.__super__.initialize.apply(this, arguments);
1211
            this.init_handlers();
1212
            this.selected_keys = [];
1213
            this.cont = this.$(".step-cont");
1214
        },
1215
        
1216
        init_subviews: function() {
1217
            var create_view = this.parent;
1218
            if (!this.networks_view) {
1219
              this.networks_view = new views.NetworkSelectView({
1220
                container: this.cont
1221
              });
1222
              this.networks_view.hide(true);
1223
            }
1224
        },
1225

    
1226
        init_handlers: function() {
1227
        },
1228

    
1229
        show: function() {
1230
            views.CreateNetworkingView.__super__.show.apply(this, arguments);
1231
            this.init_subviews();
1232
            this.update_layout();
1233
            this.networks_view.show(true);
1234
        },
1235
        
1236
        hide_step: function() {
1237
            this.networks_view && this.networks_view.hide(true);
1238
        },
1239

    
1240
        update_layout: function() {
1241
        },
1242

    
1243
        reset: function() {
1244
            this.selected_keys = [];
1245
            this.update_layout();
1246
        },
1247
        
1248
        get_selected_networks: function() {
1249
            if (!this.networks_view) { return [] }
1250
            return this.networks_view.get_selected_networks();
1251
        },
1252
        
1253
        get_selected_addresses: function() {
1254
            if (!this.networks_view) { return [] }
1255
            return this.networks_view.get_selected_floating_ips();
1256
        },
1257

    
1258
        get: function() {
1259
            return {
1260
              'addresses': this.get_selected_addresses(),
1261
              'networks': this.get_selected_networks()
1262
            }
1263
        },
1264

    
1265
        remove: function() {
1266
          if (this.networks_view) {
1267
            this.networks_view.remove();
1268
            delete this.networks_view;
1269
          }
1270
        }
1271
    });
1272

    
1273
    views.CreatePersonalizeView = views.CreateVMStepView.extend({
1274
        step: 4,
1275
        initialize: function() {
1276
            views.CreateSubmitView.__super__.initialize.apply(this, arguments);
1277
            this.roles = this.$("li.predefined-meta.role .values");
1278
            this.name = this.$("input.rename-field");
1279
            this.name_changed = false;
1280
            this.init_suggested_roles();
1281
            this.init_handlers();
1282
            this.ssh_list = this.$(".ssh ul");
1283
            this.selected_keys = [];
1284

    
1285
            var self = this;
1286
        },
1287

    
1288
        init_suggested_roles: function() {
1289
            var cont = this.roles;
1290
            cont.empty();
1291
            
1292
            // TODO: get suggested from snf.api.conf
1293
            _.each(window.SUGGESTED_ROLES, function(r){
1294
                var el = $('<span class="val">{0}</span>'.format(_.escape(r)));
1295
                el.data("value", r);
1296
                cont.append(el);
1297
                el.click(function() {
1298
                    $(this).parent().find(".val").removeClass("selected");
1299
                    $(this).toggleClass("selected");
1300
                })
1301
            });
1302
            
1303
            var self = this;
1304
            $(".ssh li.ssh-key-option").live("click", function(e) {
1305
                var key = $(this).data("model");
1306
                self.select_key(key);
1307
            });
1308
        },
1309

    
1310
        select_key: function(key) {
1311
            var exists = this.selected_keys.indexOf(key.id);
1312
            if (exists > -1) {
1313
                this.selected_keys.splice(exists, 1);
1314
            } else {
1315
                this.selected_keys.push(key.id);
1316
            }
1317
            this.update_ui_keys_selections(this.selected_keys);
1318
        },
1319

    
1320
        update_ui_keys_selections: function(keys) {
1321
            var self = this;
1322
            self.$(".ssh-key-option").removeClass("selected");
1323
            self.$(".ssh-key-option .check").attr("checked", false);
1324
            _.each(keys, function(kid) {
1325
                $("#ssh-key-option-" + kid).addClass("selected");
1326
                $("#ssh-key-option-" + kid).find(".check").attr("checked", true);
1327
            });
1328
        },
1329

    
1330
        update_ssh_keys: function() {
1331
            this.ssh_list.empty();
1332
            var keys = snf.storage.keys.models;
1333
            if (keys.length == 0) { 
1334
                this.$(".ssh .empty").show();
1335
            } else {
1336
                this.$(".ssh .empty").hide();
1337
            }
1338
            _.each(keys, _.bind(function(key){
1339
                var name = _.escape(util.truncate(key.get("name"), 45));
1340
                var el = $('<li id="ssh-key-option-{1}" class="ssh-key-option">{0}</li>'.format(name, key.id));
1341
                var check = $('<input class="check" type="checkbox"></input>')
1342
                el.append(check);
1343
                el.data("model", key);
1344
                this.ssh_list.append(el);
1345
            }, this));
1346
        },
1347

    
1348
        init_handlers: function() {
1349
            this.name.bind("keypress", _.bind(function(e) {
1350
                this.name_changed = true;
1351
                if (e.keyCode == 13) { this.parent.set_step(5); this.parent.update_layout() };    
1352
            }, this));
1353

    
1354
            this.name.bind("click", _.bind(function() {
1355
                if (!this.name_changed) {
1356
                    this.name.val("");
1357
                }
1358
            }, this))
1359
        },
1360

    
1361
        show: function() {
1362
            views.CreatePersonalizeView.__super__.show.apply(this, arguments);
1363
            this.update_layout();
1364
        },
1365
        
1366
        update_layout: function() {
1367
            var params = this.parent.get_params();
1368

    
1369
            if (!params.image || !params.flavor) { return }
1370

    
1371
            if (!params.image) { return }
1372
            var vm_name_tpl = snf.config.vm_name_template || "My {0} server";
1373
            //if (params.image.is_snapshot()) { vm_name_tpl = "{0}" };
1374
            var vm_name = vm_name_tpl.format(params.image.get("name"));
1375
            var orig_name = vm_name;
1376
            
1377
            var existing = true;
1378
            var j = 0;
1379

    
1380
            while (existing && !this.name_changed) {
1381
                var existing = storage.vms.select(function(vm){
1382
                  return vm.get("name") == vm_name
1383
                }).length;
1384
                if (existing) {
1385
                    j++;
1386
                    vm_name = orig_name + " " + j;
1387
                }
1388
            }
1389

    
1390
            if (!_(this.name.val()).trim() || !this.name_changed) {
1391
                this.name.val(vm_name);
1392
            }
1393

    
1394
            if (!this.name_changed && this.parent.visible()) {
1395
                if (!$.browser.msie && !$.browser.opera) {
1396
                    this.$("#create-vm-name").select();
1397
                } else {
1398
                    window.setTimeout(_.bind(function(){
1399
                        this.$("#create-vm-name").select();
1400
                    }, this), 400)
1401
                }
1402
            }
1403
            
1404
            var img = snf.ui.helpers.os_icon_path(params.image.get("OS"))
1405
            this.name.css({backgroundImage:"url({0})".format(img)})
1406
            
1407
            if (!params.image.supports('ssh')) {
1408
                this.disable_ssh_keys();
1409
            } else {
1410
                this.enable_ssh_keys();
1411
                this.update_ssh_keys();
1412
            }
1413

    
1414
            this.update_ui_keys_selections(this.selected_keys);
1415
        },
1416

    
1417
        disable_ssh_keys: function() {
1418
            this.$(".disabled.desc").show();
1419
            this.$(".empty.desc").hide();
1420
            this.$(".ssh .confirm-params").hide();
1421
            this.selected_keys = [];
1422
        },
1423

    
1424
        enable_ssh_keys: function() {
1425
            this.$(".ssh .confirm-params").show();
1426
            this.$(".disabled.desc").hide();
1427
        },
1428

    
1429
        reset: function() {
1430
            this.roles.find(".val").removeClass("selected");
1431
            this.name_changed = false;
1432
            this.selected_keys = [];
1433
            this.update_layout();
1434
        },
1435

    
1436
        get_meta: function() {
1437
            if (this.roles.find(".selected").length == 0) {
1438
                return false;
1439
            }
1440

    
1441
            var role = $(this.roles.find(".selected").get(0)).data("value");
1442
            return {'Role': role }
1443
        },
1444

    
1445
        get: function() {
1446
            var val = {'name': this.name.val() };
1447
            if (this.get_meta()) {
1448
                val.metadata = this.get_meta();
1449
            }
1450

    
1451
            val.keys = _.map(this.selected_keys, function(k){ return snf.storage.keys.get(k)});
1452
            
1453
            return val;
1454
        }
1455
    });
1456

    
1457
    views.CreateSubmitView = views.CreateVMStepView.extend({
1458
        step: 5,
1459
        initialize: function() {
1460
            views.CreateSubmitView.__super__.initialize.apply(this, arguments);
1461
            this.roles = this.$("li.predefined-meta.role .values");
1462
            this.confirm = this.$(".confirm-params ul");
1463
            this.name = this.$("h3.vm-name");
1464
            this.keys = this.$(".confirm-params.ssh");
1465
            this.meta = this.$(".confirm-params.meta");
1466
            this.ip_addresses = this.$(".confirm-params.ip-addresses");
1467
            this.private_networks = this.$(".confirm-params.private-networks");
1468
            this.init_handlers();
1469
        },
1470

    
1471
        init_handlers: function() {
1472
        },
1473

    
1474
        show: function() {
1475
            views.CreateSubmitView.__super__.show.apply(this, arguments);
1476
            this.update_layout();
1477
        },
1478
        
1479
        update_network_details: function() {
1480
            var data = this.parent.get_params();
1481
            var ips = data.addresses;
1482
            var networks = data.networks;
1483

    
1484
            this.ip_addresses.empty();
1485
            if (!ips|| ips.length == 0) {
1486
                this.ip_addresses.append(this.make("li", {'class':'empty'}, 
1487
                                           'No ip addresses selected'))
1488
            }
1489
            _.each(ips, _.bind(function(ip) {
1490
                var el = this.make("li", {'class':'selected-ip-address'}, 
1491
                                  ip.get('floating_ip_address'));
1492
                this.ip_addresses.append(el);
1493
            }, this))
1494

    
1495
            this.private_networks.empty();
1496
            if (!networks || networks.length == 0) {
1497
                this.private_networks.append(this.make("li", {'class':'empty'}, 
1498
                                             'No private networks selected'))
1499
            }
1500
            _.each(networks, _.bind(function(network) {
1501
                var el = this.make("li", {'class':'selected-private-network'}, 
1502
                                  network.get('name'));
1503
                this.private_networks.append(el);
1504
            }, this))
1505

    
1506
        },
1507

    
1508
        update_flavor_details: function() {
1509
            var flavor = this.parent.get_params().flavor;
1510

    
1511
            function set_detail(sel, key) {
1512
                var val = key;
1513
                if (key == undefined) { val = flavor.get(sel) };
1514
                this.$(".confirm-cont.flavor .flavor-" + sel + " .value").text(val)
1515
            }
1516
            
1517
            set_detail("cpu", flavor.get("cpu") + "x");
1518
            set_detail("ram", flavor.get("ram") + " MB");
1519
            set_detail("disk", util.readablizeBytes(flavor.get("disk") * 1024 * 1024 * 1024));
1520
            set_detail("disktype", flavor.get_disk_template_info().name);
1521
        },
1522

    
1523
        update_image_details: function() {
1524
            var image = this.parent.get_params().image;
1525

    
1526
            function set_detail(sel, key) {
1527
                var val = key;
1528
                if (key == undefined) { val = image.get(sel) };
1529
                this.$(".confirm-cont.image .image-" + sel + " .value").text(val)
1530
            }
1531
            
1532
            set_detail("description", image.get_description());
1533
            set_detail("name", util.truncate(image.get("name"), 30));
1534
            set_detail("os", _(image.get_os()).capitalize());
1535
            set_detail("gui", image.get_gui());
1536
            set_detail("size", _.escape(image.get_readable_size()));
1537
            set_detail("kernel");
1538
        },
1539

    
1540
        update_selected_keys: function(keys) {
1541
            this.keys.empty();
1542
            if (!keys || keys.length == 0) {
1543
                this.keys.append(this.make("li", {'class':'empty'}, 'No keys selected'))
1544
            }
1545
            _.each(keys, _.bind(function(key) {
1546
                var name = _.escape(util.truncate(key.get("name"), 20))
1547
                var el = this.make("li", {'class':'selected-ssh-key'}, name);
1548
                this.keys.append(el);
1549
            }, this))
1550
        },
1551

    
1552
        update_selected_meta: function(meta) {
1553
            this.meta.empty();
1554
            if (!meta || meta.length == 0) {
1555
                this.meta.append(this.make("li", {'class':'empty'}, 'No tags selected'))
1556
            }
1557
            _.each(meta, _.bind(function(value, key) {
1558
                var el = this.make("li", {'class':'confirm-value'});
1559
                var name = this.make("span", {'class':'ckey'}, key);
1560
                var value = this.make("span", {'class':'cval'}, value);
1561

    
1562
                $(el).append(name)
1563
                $(el).append(value);
1564
                this.meta.append(el);
1565
            }, this));
1566
        },
1567

    
1568
        update_layout: function() {
1569
            var params = this.parent.get_params();
1570
            if (!params.image || !params.flavor) { return }
1571

    
1572
            if (!params.image) { return }
1573

    
1574
            this.name.text(util.truncate(params.name, 50));
1575

    
1576
            this.confirm.find("li.image .value").text(params.flavor.get("image"));
1577
            this.confirm.find("li.cpu .value").text(params.flavor.get("cpu") + "x");
1578
            this.confirm.find("li.mem .value").text(params.flavor.get("ram"));
1579
            this.confirm.find("li.disk .value").text(params.flavor.get("disk"));
1580

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

    
1584
            this.update_image_details();
1585
            this.update_flavor_details();
1586
            this.update_network_details();
1587

    
1588
            if (!params.image.supports('ssh')) {
1589
                this.keys.hide();
1590
                this.keys.prev().hide();
1591
            } else {
1592
                this.keys.show();
1593
                this.keys.prev().show();
1594
                this.update_selected_keys(params.keys);
1595
            }
1596
            
1597
            this.update_selected_meta(params.metadata);
1598
        },
1599

    
1600
        reset: function() {
1601
            this.update_layout();
1602
        },
1603

    
1604
        get_meta: function() {
1605
        },
1606

    
1607
        get: function() {
1608
            return {};
1609
        }
1610
    });
1611

    
1612
    views.CreateVMView = views.Overlay.extend({
1613
        
1614
        view_id: "create_vm_view",
1615
        content_selector: "#createvm-overlay-content",
1616
        css_class: 'overlay-createvm overlay-info',
1617
        overlay_id: "metadata-overlay",
1618

    
1619
        subtitle: false,
1620
        title: "Create new machine",
1621

    
1622
        initialize: function(options) {
1623
            views.CreateVMView.__super__.initialize.apply(this);
1624
            this.current_step = 1;
1625

    
1626
            this.password_view = new views.VMCreationPasswordView();
1627

    
1628
            this.steps = [];
1629
            this.steps[1] = new views.CreateImageSelectView(this);
1630
            this.steps[1].bind("change", _.bind(function(data) {this.trigger("image:change", data)}, this));
1631

    
1632
            this.steps[2] = new views.CreateFlavorSelectView(this);
1633
            this.steps[3] = new views.CreateNetworkingView(this);
1634
            this.steps[4] = new views.CreatePersonalizeView(this);
1635
            this.steps[5] = new views.CreateSubmitView(this);
1636

    
1637
            this.cancel_btn = this.$(".create-controls .cancel");
1638
            this.next_btn = this.$(".create-controls .next");
1639
            this.prev_btn = this.$(".create-controls .prev");
1640
            this.submit_btn = this.$(".create-controls .submit");
1641

    
1642
            this.history = this.$(".steps-history");
1643
            this.history_steps = this.$(".steps-history .steps-history-step");
1644

    
1645
            this.loading_view = $("<div>Loading images...</div>");
1646
            this.$(".container").after(this.loading_view);
1647
            this.loading_view.css({
1648
              backgroundColor: "#97C3D6", 
1649
              padding: '15px',
1650
              fontSize: '0.7em',
1651
              color: '#333'
1652
            });
1653
            
1654
            this.init_handlers();
1655
        },
1656

    
1657
        init_handlers: function() {
1658
            var self = this;
1659
            this.next_btn.click(_.bind(function(){
1660
                this.set_step(this.current_step + 1);
1661
                this.update_layout();
1662
            }, this))
1663
            this.prev_btn.click(_.bind(function(){
1664
                this.set_step(this.current_step - 1);
1665
                this.update_layout();
1666
            }, this))
1667
            this.cancel_btn.click(_.bind(function(){
1668
                this.close_all();
1669
            }, this))
1670
            this.submit_btn.click(_.bind(function(){
1671
                this.submit();
1672
            }, this))
1673
            
1674
            this.history.find(".completed").live("click", function() {
1675
                var step = parseInt($(this).attr("id").replace("vm-create-step-history-", ""));
1676
                self.set_step(step);
1677
                self.update_layout();
1678
            })
1679
        },
1680

    
1681
        set_step: function(st) {
1682
        },
1683
        
1684
        validate: function(data) {
1685
            if (_(data.name).trim() == "") {
1686
                this.$(".form-field").addClass("error");
1687
                return false;
1688
            } else {
1689
                return true;
1690
            }
1691
        },
1692

    
1693
        submit: function() {
1694
            if (this.submiting) { return };
1695
            var data = this.get_params();
1696
            var meta = {};
1697
            var extra = {};
1698
            var personality = [];
1699

    
1700
            if (this.validate(data)) {
1701
                this.submit_btn.addClass("in-progress");
1702
                this.submiting = true;
1703
                if (data.metadata) { meta = data.metadata; }
1704
                if (data.keys && data.keys.length > 0) {
1705
                    personality.push(
1706
                      data.image.personality_data_for_keys(data.keys))
1707
                }
1708

    
1709
                if (personality.length) {
1710
                    extra['personality'] = _.flatten(personality);
1711
                }
1712
                
1713
                extra['networks'] = [];
1714
                _.each(data.networks, function(n) {
1715
                  extra.networks.push({'uuid': n.get('id')})
1716
                });
1717
                _.each(data.addresses, function(ip) {
1718
                  extra.networks.push({
1719
                    'uuid': ip.get('network').get('id'),
1720
                    'fixed_ip': ip.get('floating_ip_address')
1721
                  });
1722
                });
1723

    
1724
                _.map(data.networks, function(n) { return n.get('id') });
1725
                storage.vms.create(data.name, data.image, data.flavor, 
1726
                                   meta, extra, _.bind(function(data){
1727
                    _.each(data.addresses, function(ip) {
1728
                      ip.set({'status': 'connecting'});
1729
                    });
1730
                    this.close_all();
1731
                    this.password_view.show(data.server.adminPass, 
1732
                                            data.server.id);
1733
                    var self = this;
1734
                    window.setTimeout(function() {
1735
                      self.submiting = false;
1736
                    }, 1000);
1737
                }, this));
1738
            }
1739
        },
1740

    
1741
        close_all: function() {
1742
          this.hide();
1743
        },
1744

    
1745
        onClose: function() {
1746
          this.steps[3].remove();
1747
        },
1748

    
1749
        reset: function() {
1750
          this.current_step = 1;
1751

    
1752
          this.steps[1].reset();
1753
          this.steps[2].reset();
1754
          this.steps[3].reset();
1755
          this.steps[4].reset();
1756

    
1757
          //this.steps[1].show();
1758
          //this.steps[2].show();
1759
          //this.steps[3].show();
1760
          //this.steps[4].show();
1761

    
1762
          this.submit_btn.removeClass("in-progress");
1763
        },
1764

    
1765
        onShow: function() {
1766
        },
1767

    
1768
        update_layout: function() {
1769
            this.show_step(this.current_step);
1770
            this.current_view.update_layout();
1771
        },
1772

    
1773
        beforeOpen: function() {
1774
            if (!this.skip_reset_on_next_open) {
1775
                this.submiting = false;
1776
                this.reset();
1777
                this.current_step = 1;
1778
                this.$(".steps-container").css({"margin-left":0 + "px"});
1779
                this.show_step(1);
1780
            }
1781

    
1782
            this.loading_view.show();
1783
            this.$(".container").hide();
1784
            var complete = _.bind(function() {
1785
              this.loading_view.hide();
1786
              this.$(".container").slideDown();
1787
              this.update_layout();
1788
            }, this);
1789

    
1790
            synnefo.storage.images.fetch({complete: complete});
1791

    
1792
            this.skip_reset_on_next_open = false;
1793
        },
1794
        
1795
        set_step: function(step) {
1796
            if (step <= 1) {
1797
                step = 1
1798
            }
1799
            if (step > this.steps.length - 1) {
1800
                step = this.steps.length - 1;
1801
            }
1802
            this.current_step = step;
1803
        },
1804

    
1805
        show_step: function(step) {
1806
            // FIXME: this shouldn't be here
1807
            // but since we are not calling step.hide this should work
1808
            this.steps[1].image_details.hide();
1809
            
1810
            this.current_view && this.current_view.hide_step && this.current_view.hide_step();
1811
            this.current_view = this.steps[step];
1812
            this.update_controls();
1813

    
1814
            this.steps[step].show();
1815
            var width = this.el.find('.container').width();
1816
            var left = (step -1) * width * -1;
1817
            this.$(".steps-container").animate({"margin-left": left + "px"}, 300);
1818

    
1819
            this.update_steps_history();
1820
        },
1821

    
1822
        update_steps_history: function() {
1823
            var self = this;
1824
            function get_step(s) {
1825
                return self.history.find(".step" + s + "h");
1826
            }
1827
            
1828
            var current_step = parseInt(this.current_view.step);
1829
            _.each(this.steps, function(stepv) {
1830
                var step = parseInt(stepv.step);
1831
                get_step(step).removeClass("completed").removeClass("current");
1832
                if (step == current_step) {
1833
                    get_step(step).removeClass("completed").addClass("current");
1834
                }
1835
                if (step < current_step) {
1836
                    get_step(step).removeClass("current").addClass("completed");
1837
                }
1838
            });
1839
        },
1840

    
1841
        update_controls: function() {
1842
            var step = this.current_step;
1843
            if (step == 1) {
1844
                this.prev_btn.hide();
1845
                this.cancel_btn.show();
1846
            } else {
1847
                this.prev_btn.show();
1848
                this.cancel_btn.hide();
1849
            }
1850
            
1851
            if (step == this.steps.length - 1) {
1852
                this.next_btn.hide();
1853
                this.submit_btn.show();
1854
            } else {
1855
                this.next_btn.show();
1856
                this.submit_btn.hide();
1857
            }
1858
        },
1859

    
1860
        get_params: function() {
1861
            return _.extend({}, this.steps[1].get(), this.steps[2].get(), this.steps[3].get(), this.steps[4].get());
1862
        }
1863
    });
1864
    
1865
})(this);
1866