Merge remote-tracking branch 'origin/enhancement/9657' into develop
[ganeti_webmgr.git] / ganeti_web / static / js / createVM_formUpdater.js
blob07bf56c385d2ede939ba785bf77c4136dd05f71a
1 function formUpdater(url_choices, url_options, url_defaults){
2     /* Live form updating for the create VM template */
3     
4     // -----------
5     // class data
6     // -----------
7     var cluster =               $("#id_cluster");
8     var owner =                 $("#id_owner");
9     var snode =                 $("#id_snode").parent("p");
10     var pnode =                 $("#id_pnode").parent("p");
11     var hypervisor =            $("#id_hypervisor");
12     var disk_type =             $("#id_disk_type");
13     var disks =                 $("#disks");
14     var disk_count =            $("#id_disk_count");
15     var disk_add =              $("#disks .add");
16     var disk_delete =           $("#disks .delete");
17     var nics =                  $("#nics");
18     var nic_count =             $("#id_nic_count");
19     var nic_add =               $("#nics .add");
20     var nic_delete =            $("#nics .delete");
21     var nic_type =              $("#id_nic_type");
22     var nic_link =              $("#nics input[name^=nic_link]");
23     var disk_template =         $("#id_disk_template");
24     var nic_mode =              $("#nics select[name^=nic_mode]");
25     var iallocator =            $("#id_iallocator");
26     var iallocator_hostname =   $("#id_iallocator_hostname");
27     var boot_order =            $("#id_boot_order");
28     var image_path =            $("#id_cdrom_image_path").parent("p");
29     var image2_path =           $("#id_cdrom2_image_path").parent("p");
30     var root_path =             $("#id_root_path");
31     var kernel_path =           $("#id_kernel_path");
32     var serial_console =        $("#id_serial_console").parent("p");
33     var no_install =            $("#id_no_install");
34     var start =                 $("#id_start").parent("p");
35     var using_str =             " Using: ";
36     var blankOptStr =           "---------";
37     var nodes =                 null; // nodes available
39     var template_choices = $("\
40             <option value=''>---------</option>\
41             <option value='plain'>plain</option>\
42             <option value='drbd'>drbd</option>\
43             <option value='file'>file</option>\
44             <option value='diskless'>diskless</option>\
45         ".toString());
47     var single_node_template_choices = $("\
48             <option value=''>---------</option>\
49             <option value='plain'>plain</option>\
50             <option value='file'>file</option>\
51             <option value='diskless'>diskless</option>\
52         ".toString());
54     // ------------
55     // cluster defaults
56     // ------------
57     var DEFAULT_NIC_MODE = undefined;
58     var DEFAULT_NIC_LINK = undefined;
60     // ------------
61     // init stuffs
62     // ------------
63     this.init = function(cluster_defaults){
64         /* initialize the live form updater */
66         // disable the iallocator stuff by default
67         if(!iallocator_hostname.attr("value")){
68             iallocator.attr("readonly", "readonly");
69         } else{
70             iallocator.after(
71                 "<span>" + 
72                     using_str + iallocator_hostname.val() +
73                 "</span>"
74             );
75         }
77         // only disable iallocator by default if there is no cluster selected
78         // or the cluster already selected does not support iallocator
79         var def_iallocator = cluster_defaults['iallocator'];
80         if (!iallocator.is(":checked")
81             && (def_iallocator == undefined|| def_iallocator == '')
82         ){
83             _iallocatorDisable();
84         }
85         
86         // hide CD-ROM Image Path stuffs by default
87         _imagePathHide();
88         
89         // setup form element change hooks
90         _initChangeHooks();
92         //recover from form error
93         if(no_install.is(":checked")){
94             start.hide();
95         }
97         // fire off some initial changes
98         cluster.change()
99         iallocator.change();
100         disk_template.change();
101         boot_order.change();
102         hypervisor.change();
104         disableSingletonDropdown($("#id_pnode"), blankOptStr);
105         // process the owner dropdown, i.e., if it only has a single option, 
106         // select it, and make the dropdown read-only
107         disableSingletonDropdown(owner, blankOptStr);
108         disableSingletonDropdown(hypervisor, blankOptStr);
109         disableSingletonDropdown(cluster, blankOptStr);
110     };
111     
112     function _initChangeHooks(){
113         /* setup change hooks for the form elements */
115         // handle hypervisor changes
116         hypervisor.live("change", function() {
117             var id = $(this).val();
118             if (id == "xen-hvm") {
119                 _showHvmKvmElements();
120                 _hidePvmKvmElements();
121                 _hideKvmElements();
122             } 
123             else if (id == "xen-pvm") {
124                 _showPvmKvmElements();
125                 _hideHvmKvmElements();
126                 _hideKvmElements(); 
127             }
128             else if (id == "kvm") {
129                 _showKvmElements();
130                 _showHvmKvmElements();
131                 _showPvmKvmElements();
132             } 
133         });
135         // boot device change
136         boot_order.live("change", function(){
137             /* 
138             Only show image path stuffs if CD-ROM is selected in the boot 
139             order dropdown.
140             */
141             var id = $(this).children("option:selected").val();
142             if(id == "cdrom"){
143                 _imagePathShow();
144             } else {
145                 _imagePathHide();
146             }
147         });
149         // iallocator change
150         iallocator.live("change", function() {
151             if(!iallocator.attr("readonly")) {
152                 if(iallocator.is(":checked")) {
153                     pnode.hide();
154                     snode.hide();
155                 } else {
156                     pnode.show();
157                     snode.show();
158                 }
159             } else {
160                 if(!iallocator.is(":checked")){
161                     pnode.show();
162                     snode.show();
163                     disk_template.parent("p").show();
164                     disk_template.change();
165                 }
166             }
167         });
169         // disk_template change
170         disk_template.live("change", function() {
171             if(!iallocator.is(":checked") || 
172                     iallocator.attr("readonly")) {
174                 if(disk_template.val() == "drbd" && nodes.length > 1){
175                     snode.show();
176                 } else {
177                     snode.hide();
178                 }
179             }
181             if (disk_template.val() == 'diskless') {
182                 disks.hide();
183                 disk_count.val(0);
184                 disks.find('input[name^=disk_size]').attr('disabled','disabled');
185             } else if (!disks.is(":visible") || true) {
186                 disks.show();
187                 disks.find('input[name^=disk_size]').removeAttr('disabled');
188                 disk_count.val(disks.find('input[name^=disk_size]').length);
189             }
190         });
192         // owner change
193         owner.live("change", function() {
194             var id = $(this).children("option:selected").val();
195             if(id != "") {
196                 // JSON update the cluster when the owner changes
197                 _cached_get(url_choices, {"clusteruser_id":id}, _update_cluster_choices);
198             }
199         });
201         //no-install change
202         no_install.live("change",function() {
203             if(no_install.is(":checked")){
204                 start.hide();
205             } 
206             else{
207                 start.show();
208             }
209         });
211         // cluster change
212         cluster.live("change", function() {
213             var id = $(this).children("option:selected").val();
214             if( id != "" ) {
215                 // JSON update oslist, pnode, and snode when cluster changes
216                 _cached_get(url_options, {"cluster_id":id}, _update_options);
218                 // only load the defaults if errors are not present 
219                 if($(".errorlist").length == 0){
220                     _fillDefaultOptions(id);
221                 }
222             }
223         });
225         disk_add.click(_add_disk);
226         disk_delete.live("click",_remove_disk);
227         nic_add.click(_add_nic);
228         nic_delete.live("click",_remove_nic);
229     }
231     // ----------------
232     // private helpers
233     // ----------------
235     function _update_cluster_choices(data){
236         var old_cluster = cluster.val();
238         cluster.children().not(":first").remove();
239         $.each(data, function(i, item) {
240             cluster.append(_newOpt(item[0], item[1]));
241         });
243         // Try to re-select the previous cluster, if possible. else just trigger
244         // a change so cluster update logic is run
245         if (cluster.children('option[value='+old_cluster+']').length) {
246             cluster.val(old_cluster);
247         } else {
248             cluster.change();
249         }
251         // process dropdown if its a singleton
252         disableSingletonDropdown(cluster, blankOptStr);
253     }
255     function _update_options(data) {
256         var pnode       = $("#id_pnode");
257         var snode       = $("#id_snode");
258         var oslist      = $("#id_os");
259         var child, child2;
260         var oldpnode = pnode.val();
261         var oldsnode = snode.val();
262         var oldos = oslist.val();
263         var old_template = disk_template.val();
265         pnode.children().not(":first").remove();
266         snode.children().not(":first").remove();
267         oslist.children().not(":first").remove();
268         $.each(data, function(i, items) {
269             $.each(items, function(key, value) {
270                 if( i == "nodes" ) {
271                     child = _newOpt(value, value);
272                     child2 = child.clone();
273                     pnode.append(child);
274                     snode.append(child2);
275                 }
276                 else if (i == "os") {
277                     child = _newOptGroup(value[0],
278                             value[1]);
279                     oslist.append(child);
280                 }
281             });
282         });
284         // make nodes publically available
285         nodes = data["nodes"];
287         // update disk template choices
288         disk_template.empty();
289         if (nodes.length == 1){
290             disk_template.html(single_node_template_choices);
291         } else {
292             disk_template.html(template_choices);
293         }
295         // Restore old choices from before, if possible.
296         pnode.val(oldpnode);
297         snode.val(oldsnode);
298         oslist.val(oldos);
299         disk_template.val(old_template);
301         // And finally, do the singleton dance.
302         disableSingletonDropdown(pnode, blankOptStr);
303         disableSingletonDropdown(snode, blankOptStr);
304         disableSingletonDropdown(oslist, blankOptStr);
305     }
307     function _update_cluster_defaults(d){
308         /* fill default options */
310         // boot device dropdown
311         if(d["boot_devices"]) {
312             boot_order.children().remove();
313             $.each(d["boot_devices"], function(i, item){
314                 boot_order.append(_newOpt(item[0], item[1]));
315             });
316         }
317         if(d["boot_order"]) {
318             boot_order.find(":selected").removeAttr(
319                 "selected");
320             boot_order.find("[value=" + d["boot_order"] + "]")
321                 .attr("selected","selected");
322             boot_order.change();
323         }
325         // hypervisors dropdown
326         if(d["hypervisors"]) {
327             hypervisor.children().remove();
328             $.each(d["hypervisors"], function(i, item){
329                 hypervisor.append(_newOpt(item[0], item[1]));
330             });
331             if(d["hypervisor"]) {
332                 if (d["hypervisor"] != "" &&
333                     d["hypervisor"] != undefined) {
334                     hypervisor.find(":selected").removeAttr("selected");
335                     hypervisor.find("[value=" + d["hypervisor"] + "]")
336                         .attr("selected", "selected");
337                     hypervisor.change();
338                 }
339             }
340             disableSingletonDropdown(hypervisor, blankOptStr);
341         }
343         // iallocator checkbox
344         if(d["iallocator"] != "" &&
345                 d["iallocator"] != undefined){
346             if(!iallocator_hostname.attr("value")) {
347                 iallocator_hostname.attr("value",
348                         d["iallocator"]);
349                 if(iallocator.siblings("span").length == 0){
350                     iallocator.after(
351                         "<span>" + using_str +
352                             d["iallocator"] +
353                         "</span>"
354                     );
355                 }
356             }
357             // Check iallocator checkbox
358             iallocator.parent("p").show();
359             iallocator.removeAttr("disabled")
360                 .removeAttr("readonly")
361                 .attr("checked", "checked")
362                 .change();
363         } else {
364             _iallocatorDisable();
365         }
367         // kernel path text box
368         if(d["kernel_path"]){
369             kernel_path.val(d["kernel_path"]);
370         }
372         // nic mode dropdown
373         if(d["nic_mode"]) {
374             nic_mode.find(":selected").removeAttr("selected");
375             nic_mode.find("[value=" + d["nic_mode"] + "]")
376                 .attr("selected","selected");
377             DEFAULT_NIC_MODE = d["nic_mode"];
378         } else {
379             nic_mode.find(":first-child")
380                 .attr("selected", "selected");
381         }
383         // nic link text box
384         if(d["nic_link"]){
385             nic_link.val(d["nic_link"]);
386             DEFAULT_NIC_LINK = d["nic_link"];
387         }
389         // nic type dropdown
390         if(d["nic_types"]) {
391             nic_type.children().remove();
392             $.each(d["nic_types"], function(i, item){
393                 nic_type.append(_newOpt(item[0], item[1]));
394             });
395         }
396         if(d["nic_type"]) {
397             nic_type.find(":selected").removeAttr("selected");
398             nic_type.find("[value=" + d["nic_type"] + "]")
399                 .attr("selected","selected");
400         }
402         // memory text box
403         if($("#id_memory") == "" && d["memory"]){
404             $("#id_memory").val(d["memory"]);
405         }
407         // disk type dropdown
408         if(d["disk_types"]){
409             disk_type.children().remove();
410             $.each(d["disk_types"], function(i, item){
411                 disk_type.append(_newOpt(item[0], item[1]));
412             });
413             if(d["disk_type"]){
414                 disk_type.val(d["disk_type"]);
415             }
416         }
418         // root path text box
419         if(d["root_path"]){
420             root_path.val(d["root_path"]);
421         } else {
422             root_path.val("/");
423         }
425         // enable serial console checkbox
426         if(d["serial_console"]){
427             $("#id_serial_console")
428                 .attr("checked", "checked");
429         } else {
430             $("#id_serial_console").removeAttr("checked");
431         }
433         // virtual CPUs text box
434         if($("#id_vcpus").val() == "" && d["vcpus"]){
435             $("#id_vcpus").val(d["vcpus"]);
436         }
438         // image path text box
439         if(d["cdrom_image_path"]){
440             image_path.find("input").val(d["cdrom_image_path"]);
441         }
442         //second cdrom
443         if(d["cdrom2_image_path"]){
444             image2_path.find("input").val(d["cdrom2_image_path"]);
445         }
446     }
448     function _fillDefaultOptions(cluster_id) {
449         var args = new Object();
450         args["cluster_id"] = cluster_id;
451         var _hypervisor = hypervisor.val();
452         if (_hypervisor != '') {
453             args["hypervisor"] = _hypervisor;
454         }
455         _cached_get(url_defaults, args, _update_cluster_defaults);
456     }
458     function _imagePathHide(){
459         image_path.hide();
460         image2_path.hide();
461     }
463     function _imagePathShow(){
464         image_path.show();
465         image2_path.show();
466     }
468     function _iallocatorDisable(){
469         /* Disable and hide all of the iallocator stuffs */
470         iallocator.parent("p").hide();
471         iallocator_hostname.removeAttr("value")
472             .parent("p").hide();
473         iallocator.attr("disabled", "disabled")
474             .removeAttr("checked")
475             .change();
476     }
478     function _newOpt(value, text) {
479         /* Create new option items for select field */
480         var o = $("<option></option>");
481         o.attr("value", value);
482         o.attr("text", text);
483         return o;
484     }
486     function _newOptGroup(value, options) {
487         /* Create new option group for select field */
488         var group = $("<optgroup></optgroup>");
489         group.attr("label", value);
490         $.each(options, function(i, option) {
491             group.append(_newOpt(option[0], option[1]));
492         });
493         return group;
494     }
495     
496     function _hideHvmKvmElements() {
497         // Hide hvm + kvm specific hypervisor fields
498         boot_order.parent("p").hide();
499         image_path.hide();
500         nic_type.parent("p").hide();
501         disk_type.parent("p").hide();
502     }
504     function _showHvmKvmElements() {
505         // Show hvm + kvm specific hypervisor fields
506         boot_order.parent("p").show();
507         boot_order.change(); 
508         nic_type.parent("p").show();
509         disk_type.parent("p").show();
510     }
512     function _hidePvmKvmElements() {
513         // Hide pvm specific hypervisor fields
514         root_path.parent("p").hide();
515         kernel_path.parent("p").hide();
516     }
518     function _showPvmKvmElements() {
519         // Show pvm specific hypervisor fields
520         root_path.parent("p").show();
521         kernel_path.parent("p").show();
522     }
524     function _hideKvmElements() {
525         // Hide kvm specific hypervisor fields
526         serial_console.hide();
527     }
529     function _showKvmElements() {
530         // Show kvm specific hypervisor fields
531         serial_console.show();
532     }
534     function _add_disk() {
535         var count = disk_count.val();
536         disk_count.val(parseInt(count)+1);
537         var p = $('<p></p>');
538         var label = $("<label>Disk/" + count+ " Size</label>");
539         label.attr('for', "id_disk_size_" + count);
540         var input = $('<input type="text"/>');
541         input.attr("name", "disk_size_" + count);
542         input.attr("id", "id_disk_size_" + count);
543         p.append(label);
544         p.append(input);
545         disks.append(p);
546         disks.append('<div class="icon delete"></div>');
547     }
549     function _remove_disk() {
550         var count = disk_count.val();
551         disk_count.val(parseInt(count)-1);
552         var button = $(this);
553         button.prev("p").remove();
554         button.prev("ul").remove();
555         button.remove();
557         // renumber remaining disks
558         var i = 0;
559         $('#disks p').each(function(){
560             $(this).children('label')
561                     .html("Disk/" + i + " Size")
562                     .attr("for", "id_disk_size_" + i);
563             $(this).children('#disks input[name^=disk_size]').each(function(){
564                 $(this)
565                     .attr("name", "disk_size_" + i)
566                     .attr("id", "id_disk_size_" + i);
567             });
568             i++;
569         });
570     }
572     function _add_nic() {
573         var count = nic_count.val();
574         nic_count.val(parseInt(count)+1);
575         var p = $('<p></p>');
576         var label = $("<label>NIC/" + count +"</label>");
578         // create mode select box
579         var mode = $('<select></select>');
580         mode.append('<option>----------</option>');
581         mode.append('<option value="bridged">bridged</option>');
582         mode.append('<option value="routed">routed</option>');
583         mode.attr("name", "nic_mode_" + count);
584         mode.attr("id", "id_nic_mode_" + count);
585         if (DEFAULT_NIC_LINK != undefined) {
586             mode.val(DEFAULT_NIC_MODE);
587         }
589         // create link input
590         var link = $("<input type='text'/>");
591         link.val(DEFAULT_NIC_LINK);
592         link.attr("name", "nic_link_" + count);
593         link.attr("id", "id_nic_link_" + count);
594         if (DEFAULT_NIC_LINK != undefined) {
595             mode.val(DEFAULT_NIC_MODE);
596         }
597         p.append(label);
598         p.append(mode);
599         p.append(link);
600         nics.append(p);
601         nics.append('<div class="icon delete"></div>');
602     }
604     function _remove_nic() {
605         var count = nic_count.val();
606         nic_count.val(parseInt(count)-1);
607         var button = $(this);
608         button.prev("p").remove();
609         button.prev("ul").remove();
610         button.remove();
612         // renumber remaining disks
613         var i = 0;
614         $('#nics p').each(function(){
615             $(this).children('label')
616                 .html("NIC/" + i);
617             $(this).children('input[name^=nic_link]').each(function(){
618                 $(this)
619                     .attr("name", "nic_link_" + i)
620                     .attr("id", "id_nic_link_" + i);
621             });
622             $(this).children('select[name^=nic_mode]').each(function(){
623                 $(this)
624                     .attr("name", "nic_mode_" + i)
625                     .attr("id", "id_nic_mode_" + i);
626             });
627             i++;
628         });
629     }
631     var AJAX_CACHE = {};
632     function _cached_get(url, data, callback) {
633         var key = _encode_url(url, data);
634         var response = AJAX_CACHE[key];
635         if (response == undefined ) {
637             // create a callback function that will execute for all calls to
638             // this url.  helps deal with multiple simultaneous calls to a url
639             var _callback = function(response, status, xhr) {
640                 AJAX_CACHE[key] = response;
641                 var callbacks = _callback.callbacks;
642                 for (var i in callbacks) {
643                     callbacks[i](response, status, xhr);
644                 }
645             };
647             // add callback to generic callback function
648             _callback.callbacks = [];
649             if (callback != undefined) {
650                 _callback.callbacks.push(callback);
651             }
652             
653             $.getJSON(url, data, _callback);
654             AJAX_CACHE[key] = _callback;
655         } else if (callback != undefined) {
656             if (response.callbacks != undefined) {
657                 _push_unique(response.callbacks, callback);
658             } else {
659                 callback(response);
660             }
661         }
662     }
664     function _push_unique(array, item) {
665        for (var i in array) {
666            if (array[i] == item) {
667                return;
668            }
669        }
670        array.push(item);
671     }
673     function _encode_url(url, data)
674     {
675        var ret = [];
676        for (var d in data)
677           ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
678        return url+'?'+ret.join("&");
679     }