Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / ui / static / snf / js / ui / web / ui_vm_resize_view.js @ 198b546d

History | View | Annotate | Download (15.9 kB)

1
// Copyright 2014 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.FlavorOptionsView = views.View.extend({
54

    
55
        choices_meta: {
56
            'cpu': {title: 'CPUs', description: 'Choose number of CPU cores'},
57
            'ram': {title: 'Memory size', description: 'Choose memory size'},
58
            'disk': {title: 'Disk size', description: 'Choose disk size'},
59
            'disk_template': {title: 'Storage', description: 'Select storage type'}
60
        },
61

    
62
        initialize: function(options) {
63
            views.FlavorOptionsView.__super__.initialize.apply(this);
64
            _.bindAll(this);
65
            this.$el = $(this.el);
66
            this.flavors = options.flavors || synnefo.storage.flavors.active();
67
            this.collection = options.collection || synnefo.storage.flavors;
68
            this.choices = options.choices || ['cpu', 'ram', 'disk', 
69
                                               'disk_template'];
70
            this.hidden_choices = options.hidden_choices || [];
71
            this.quotas = options.quotas || synnefo.storage.quotas;
72
            this.selected_flavor = options.selected_flavor || undefined;
73
            this.extra_quotas = options.extra_quotas || undefined;
74
            this.project = options.project;
75
            this.render();
76
            if (this.selected_flavor) { this.set_flavor(this.selected_flavor)}
77
        },
78

    
79
        render: function() {
80
            this.$el.empty();
81
            this.each_choice(function(choice) {
82
                var el = this[choice + '_el'] = $("<div>");
83
                el.addClass("flavor-options clearfix");
84
                el.addClass(choice);
85
                var title = $("<h4 class='clearfix'><span class='title'>" + 
86
                              "</span><span class='available'></span>" +
87
                              "<span class='desc'></span></h4>");
88
                el.append(title);
89
                el.find("span.title").text(this.choices_meta[choice].title);
90
                el.find("span.desc").text(this.choices_meta[choice].description);
91
                var ul = $("<ul/>");
92
                ul.addClass("flavors-"+choice+"-list flavor-opts-list clearfix");
93
                el.append(ul);
94
                this.$el.append(el);
95
                if (this.hidden_choices.indexOf(choice) > -1) {
96
                    el.hide();
97
                }
98
            });
99
            this.update_quota_left();
100
            this.fill();
101
            this.set_unavailable();
102
            this.init_handlers();
103
        },
104
        
105
        get_current: function(choice, value) {
106
          var found = false;
107
          _.each(this.flavors, _.bind(function(f){
108
              if (found) { return }
109
              if (f.get(choice) == value) {
110
                  found = true;
111
                  to_select = f;
112
              }
113
          }, this));
114
        },
115

    
116
        init_handlers: function() {
117
            this.$el.on('click', 'li.choice', _.bind(function(e) {
118
                var el = $(e.target).closest('li');
119
                var choice = el.data('type');
120
                var value = el.data('value');
121
                var to_select = this.selected_flavor;
122
                if (to_select) {
123
                    var attrs = _.clone(to_select.attributes);
124
                    attrs[choice] = value;
125
                    to_select = this.collection.get_flavor(attrs.cpu, attrs.ram, 
126
                                                           attrs.disk, 
127
                                                           attrs.disk_template);
128
                }
129

    
130
                if (!to_select) {
131
                  to_select = this.get_current(choice, value);
132
                }
133
                this.set_flavor(to_select);
134
            }, this));
135
        },
136
        
137
        update_quota_left: function() {
138
            this.each_choice(function(choice){
139
                var el = this[choice + '_el'].find(".available");
140
                el.removeClass("error");
141
                var quota = this.quotas.get('cyclades.'+choice);
142
                if (!quota) { return }
143
                var type = choice;
144
                var active = true;
145
                var key = 'available';
146
                var quotas = synnefo.storage.quotas;
147
                var available_dsp = quotas.get('cyclades.'+type).get_readable(key, active);
148
                var available = quotas.get('cyclades.'+type).get(key);
149
                var content = "({0} left)".format(available_dsp);
150
                if (available <= 0) { content = "(None left)"; el.addClass("error") }
151
                el.text(content);
152
            });
153
        },
154

    
155
        metric_for_choice: function(choice) {
156
            var map = {ram:'MB', cpu:'X', disk:'GB', disk_template:''};
157
            return map[choice] || '';
158
        },
159
        
160
        set_unavailable: function() {
161
            this.$el.find("li.choice").removeClass("disabled");
162
            var quotas = this.project.quotas.get_available_for_vm({'active': true});
163
            var extra_quotas = this.extra_quotas;
164
            var user_excluded = storage.flavors.unavailable_values_for_quotas(
165
              quotas, 
166
              storage.flavors.active(), extra_quotas);
167
            _.each(user_excluded, _.bind(function(values, key) {
168
                _.each(values, _.bind(function(value) {
169
                    var choice_el = this.select_choice(key, value);
170
                    choice_el.addClass("disabled").removeClass("selected");
171
                }, this));
172
            }, this));
173
        },
174

    
175
        select_choice: function(key, value) {
176
            return this.$el.find(".choice[data-type="+key+"][data-value="+value+"]");
177
        },
178

    
179
        fill: function(flavors) {
180
            var flavors = flavors || this.flavors;
181
            var data = this.collection.get_data(flavors);
182
            this.each_choice(function(choice) {
183
                var el = this[choice + '_el'].find("ul");
184
                el.empty();
185
                var key = choice;
186
                if (key == 'ram') { key = 'mem'}
187
                var values = data[key];
188
                if (!values) { return }
189
                _.each(values, _.bind(function(value) {
190
                    var entry = $("<li class='choice choice-" + choice + "' " +
191
                                  "data-value=" + value +
192
                                  " data-type=" + choice + ">" +
193
                                  "<span class='value'></span>" +
194
                                  "<span class='metric'></span></li>");
195
                    entry.find(".value").text(value);
196
                    entry.find(".metric").text(this.metric_for_choice(choice));
197
                    el.append(entry);
198
                    el.attr('value', value);
199
                }, this));
200
            });
201
        },
202

    
203
        set_flavor: function(flavor) {
204
            this.$el.find("li").removeClass("selected");
205
            if (!flavor) {this.selected_flavor = undefined; return}
206
            var no_select = false;
207
            var self = this;
208
            this.each_choice(function(choice){
209
                var el = this[choice + '_el'];
210
                var choice = el.find('.choice-'+choice+'[data-value='+flavor.get(choice)+']');
211
                choice.addClass("selected");
212
            });
213
            this.selected_flavor = flavor;
214
            this.trigger("flavor:select", this.selected_flavor);
215
            return this.selected_flavor;
216
        },
217

    
218
        each_choice: function(f) {
219
            return _.each(this.choices, _.bind(f, this));
220
        }
221
    });
222

    
223
    views.VmResizeView = views.Overlay.extend({
224
        
225
        view_id: "vm_resize_view",
226
        content_selector: "#vm-resize-overlay-content",
227
        css_class: 'overlay-vm-resize overlay-info',
228
        overlay_id: "vm-resize-overlay",
229

    
230
        subtitle: "",
231
        title: "Resize Machine",
232

    
233
        initialize: function(options) {
234
            this.flavors_view = undefined; 
235
            views.VmResizeView.__super__.initialize.apply(this);
236
            _.bindAll(this);
237
            this.submit = this.$(".form-action.resize");
238
            this.shutdown = this.$(".form-action.shutdown");
239
            this.pre_init_handlers();
240
            this.handle_shutdown_complete = _.bind(this.handle_shutdown_complete, this);
241
        },
242

    
243
        pre_init_handlers: function() {
244
            this.submit.click(_.bind(function(){
245
                if (this.submit.hasClass("disabled")) {
246
                    return;
247
                };
248
                this.submit_resize(this.flavors_view.selected_flavor);
249
            }, this));
250
            this.shutdown.click(_.bind(this.handle_shutdown, this));
251
        },
252
        
253
        handle_shutdown: function() {
254
          if (this.shutdown.hasClass("in-progress") || 
255
              this.shutdown.hasClass("disabled")) {
256
            return;
257
          }
258
          
259
          this.shutdown.addClass("in-progress");
260

    
261
          this.vm.unbind("change:status", this.handle_shutdown_complete);
262
          this.vm.bind("change:status", this.handle_shutdown_complete);
263

    
264
          var self = this;
265
          this.vm.call("shutdown", undefined, function() {
266
            self.shutdown.removeClass("in-progress");
267
            self.update_layout();
268
            self.hide();
269
          });
270
        },
271

    
272
        handle_shutdown_complete: function(vm) {
273
          if (!vm.is_active()) {
274
            this.shutdown.removeClass("in-progress");
275
            this.vm.unbind("change:status", this.handle_shutdown_complete);
276
          }
277
        },
278

    
279
        submit_resize: function(flv) {
280
            if (this.submit.hasClass("in-progress")) { return }
281
            this.submit.addClass("in-progress");
282
            var complete = _.bind(function() {
283
              this.vm.set({'flavor': flv});
284
              this.vm.set({'flavorRef': flv.id});
285
              this.hide();
286
            }, this);
287
            this.vm.call("resize", complete, complete, {flavor:flv.id});
288
        },
289
        
290
        show_with_warning: function(vm) {
291
          this.show(vm);
292
          this.start_warning.show();
293
        },
294

    
295
        show: function(vm) {
296
            this.start_warning = this.$(".warning.start").hide();
297
            this.start_warning.hide();
298
            this.submit.removeClass("in-progress");
299
            this.vm = vm;
300
            this.project = vm.get('project');
301
            this.vm.bind("change", this.handle_vm_change);
302
            if (this.flavors_view) {
303
                this.flavors_view.remove();
304
            }
305
            this.warning = this.$(".warning.shutdown");
306
            this.warning.hide();
307
            this.submit.show();
308
            this.shutdown.removeClass("in-progress");
309
            this.$(".flavor-options-inner-cont").append("<div>");
310
            var extra_quota = this.vm.get_flavor_quotas();
311
            if (!this.vm.is_active()) {
312
              extra_quota = undefined;
313
            }
314
            this.flavors_view = new snf.views.FlavorOptionsView({
315
                flavors:this.vm.get_resize_flavors(),
316
                el: this.$(".flavor-options-inner-cont div"),
317
                hidden_choices:['disk', 'disk_template'],
318
                selected_flavor: this.vm.get_flavor(),
319
                extra_quotas: extra_quota,
320
                project: this.project
321
            });
322
            this.selected_flavor = this.vm.get_flavor();
323
            this.handle_flavor_select(this.selected_flavor);
324
            this.flavors_view.bind("flavor:select", this.handle_flavor_select)
325
            this.submit.addClass("disabled");
326
            views.VmResizeView.__super__.show.apply(this);
327
        },
328

    
329
        handle_flavor_select: function(flv) {
330
            this.selected_flavor = flv;
331
            if (!flv || (flv.id == this.vm.get_flavor().id)) {
332
                this.submit.addClass("disabled");
333
                if (!this.shutdown.hasClass("in-progress")) {
334
                  this.shutdown.addClass("disabled");
335
                }
336
            } else {
337
                if (this.vm.can_resize()) {
338
                  this.submit.removeClass("disabled");
339
                } else {
340
                  this.shutdown.removeClass("disabled hidden");
341
                  this.warning.show();
342
                }
343
            }
344
            if (flv && !this.vm.can_start(flv, true)) {
345
              if (!this.vm.is_active()) {
346
                this.start_warning.show();
347
              }
348
            } else {
349
              this.start_warning.hide();
350
            }
351
            this.update_vm_status();
352
        },
353

    
354
        update_vm_status: function() {
355
          if (this.vm.get("status") == "STOPPED") {
356
            this.warning.hide();
357
          }
358
          if (this.vm.get("status") == "SHUTDOWN") {
359
            this.shutdown.addClass("in-progress").removeClass("disabled");
360
            this.warning.hide();
361
          }
362
        },
363

    
364
        beforeOpen: function() {
365
            this.update_layout();
366
            this.init_handlers();
367
        },
368

    
369
        update_layout: function() {
370
            this.update_actions();
371
            this.update_vm_details();
372
            this.render_choices();
373
            this.update_vm_status();
374
        },
375

    
376
        update_actions: function() {
377
          if (!this.vm.can_resize()) {
378
            this.shutdown.show();
379
            this.warning.show();
380
            this.shutdown.removeClass("disabled");
381
            if (this.selected_flavor) {
382
              this.handle_flavor_select(this.selected_flavor);
383
            } else {
384
              if (!this.shutdown.hasClass("in-progress")) {
385
                this.shutdown.addClass("disabled");
386
              }
387
            }
388
            this.submit.addClass("disabled");
389
          } else {
390
            if (this.selected_flavor && this.selected_flavor.id != this.vm.get_flavor().id) {
391
              this.submit.removeClass("disabled");
392
            }
393
            this.shutdown.hide();
394
          }
395
        },
396
          
397
        render_choices: function() {
398
        },
399

    
400
        update_vm_details: function() {
401
            var name = _.escape(util.truncate(this.vm.get("name"), 70));
402
            this.set_subtitle(name + snf.ui.helpers.vm_icon_tag(this.vm, "small"));
403
        },
404

    
405
        handle_vm_change: function() {
406
          this.update_layout();
407
        },
408

    
409
        init_handlers: function() {
410
        },
411

    
412
        onClose: function() {
413
            this.editing = false;
414
            this.vm.unbind("change", this.handle_vm_change);
415
            this.vm.unbind("change:status", this.handle_shutdown_complete);
416
            this.vm = undefined;
417
        }
418
    });
419
    
420
})(this);
421