cloudstack/ui/scripts/ui-custom/instanceWizard.js
Abhishek Kumar 2020bfb6a3 server: allows compute offering with or without constraints (#3245)
Problem: Custom compute offering does not allow setting min and max values for CPU and VRAM for custom VMs.

Root Cause: Custom compute offerings cannot be created with a given range of CPU number and memory instead it allows only fixed values.

Solution: createServiceOffering API has been modified to allow setting a defined range for CPU number and memory. Also, UI form for compute offering creation is provided with a new field named 'compute offering type’ with values - Fixed, Custom Constrained, Custom Constrained. It will allow the creation of compute offerings either with a fixed CPU speed and memory for fixed compute offering, or with a range of CPU number and memory for custom constrained compute offering or without predefined CPU number, CPU speed and memory for custom unconstrained compute offering.

To allow the user to set CPU number, CPU speed and memory during VM deployment, UI form for VM deployment has been modified to provide controls to change these values. These controls are depicted in screenshots below for custom constrained and custom unconstrained compute offering types.

Sample API calls using cmk to create a constrained service offering and deploying a VM using it,

create serviceoffering name=Constrained displaytext=Constrained customized=true mincpunumber=2 maxcpunumber=4 cpuspeed=400 minmemory=256 maxmemory=1024

deploy virtualmachine displayname=ConstrainedVM serviceofferingid=60f3e500-6559-40b2-9a61-2192891c2bd6 templateid=8e0f4a3e-601b-11e9-9df4-a0afbd4a2d60 zoneid=9612a0c6-ed28-4fae-9a48-6eb207af29e3 details[0].cpuNumber=3 details[0].memory=800

Signed-off-by: Abhishek Kumar <abhishek.kumar@shapeblue.com>
2019-05-23 11:47:53 +05:30

1440 lines
78 KiB
JavaScript

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
(function($, cloudStack) {
/**
* Instance wizard
*/
cloudStack.uiCustom.instanceWizard = function(args) {
return function(listViewArgs) {
var context = listViewArgs.context;
var instanceWizard = function(data) {
var $wizard = $('#template').find('div.instance-wizard').clone();
var $progress = $wizard.find('div.progress ul li');
var $steps = $wizard.find('div.steps').children().hide();
var $diagramParts = $wizard.find('div.diagram').children().hide();
var $form = $wizard.find('form');
$form.validate();
// Close instance wizard
var close = function() {
$wizard.dialog('destroy');
$('div.overlay').fadeOut(function() {
$('div.overlay').remove();
});
};
// Save instance and close wizard
var completeAction = function() {
var data = cloudStack.serializeForm($form);
var $wizardLoading = $('<div>').addClass('loading-overlay').appendTo($wizard).css('z-index', '10000');
// Pass network IPs
data['my-network-ips'] = [];
$form.find('.my-networks .select .specify-ip input[type=text]').each(function() {
var $input = $(this);
if (!$input.closest('.select').find('input[type=checkbox]').is(':checked')) return true;
data['my-network-ips'].push(
$input.closest('.select').hasClass('advanced') ?
$input.val() : null
);
});
data['new-network-ip'] = $form.find('.new-network .select.advanced .specify-ip input[type=text]').val();
// Handle multi-disk service offerings
if ($form.find('.multi-disk-select-container').length) {
data['disk-offerings-multi'] = [];
var $diskGroups = $form.find('.disk-select-group');
var $selectedDisks = $.grep($diskGroups, function (diskGroup) {
return $(diskGroup).find('input[type=checkbox]:checked').length;
});
$selectedDisks.map(function (disk) {
data['disk-offerings-multi'].push(
$.extend($(disk).data('json-obj'), {
_diskOfferingId: $(disk).find('input[type=radio]:checked').val()
})
);
});
}
args.action({
// Populate data
context: context,
data: data,
$wizard: $wizard,
response: {
success: function(args) {
var $listView = $('.list-view.instances');
if ($listView.length) {
var $loading = $('.list-view.instances').listView('prependItem', {
data: [{
name: data.displayname ? data.displayname : _l('label.new.vm'),
zonename: $wizard.find('select[name=zoneid] option').filter(function() {
return $(this).val() == data.zoneid;
}).html(),
state: 'Creating'
}],
actionFilter: function(args) {
return [];
}
});
}
listViewArgs.complete({
_custom: args._custom,
messageArgs: cloudStack.serializeForm($form),
$item: $listView.length ? $loading : $('<div>')
});
close();
},
error: function(message) {
$wizard.remove();
$('div.overlay').remove();
if (message) {
cloudStack.dialog.notice({
message: message
});
}
}
}
});
};
var makeSelects = function(name, data, fields, options, selectedObj, selectedObjNonEditable) {
var $selects = $('<div>');
options = options ? options : {};
$(data).each(function() {
var item = this;
var id = item[fields.id];
var $select = $('<div>')
.addClass('select')
.append(
$('<input>')
.attr({
type: (function(type) {
return type ? type : 'radio';
})(options ? options.type : null),
name: name,
'wizard-field': options['wizard-field']
})
.val(id)
.click(function() {
var $select = $(this).closest('.select');
var isSingleSelect = $select.hasClass('single-select');
var $radio = $select.find('input[type=radio]');
var $newNetwork = $(this).closest('.content').find('.select.new-network');
var $otherSelects = $select.siblings().filter(':visible');
var isCheckbox = $(this).attr('type') == 'checkbox';
if (isCheckbox) {
if (isSingleSelect) {
$select.siblings('.single-select:visible').find('input[type=checkbox]')
.attr('checked', false);
var $checkedOtherSelect = $otherSelects.filter(function() {
return $(this).not('.single-select') &&
$(this).find('input[type=checkbox]:checked').length &&
$(this).find('input[type=radio]:checked').length;
});
if (!$checkedOtherSelect.length &&
!$('.new-network:visible input[type=radio]:checked').length) {
$(this).closest('.select').find('input[type=radio]').click();
}
}
if ((!$otherSelects.length) &&
$newNetwork.find('input[type=checkbox]').is(':unchecked')) {
// Set as default
$(this).closest('.select').find('input[type=radio]').click();
}
}
if ($radio.is(':checked') && !$(this).is(':checked')) {
if (!$radio.closest('.select').index()) {
return false;
} else {
$otherSelects.filter(':first')
.find('input[type=radio]').click();
}
}
return true;
})
)
.append(
$('<div>').addClass('select-desc')
.append($('<div>').addClass('name').html(_s(this[fields.name])))
.append($('<div>').addClass('desc').html(_s(this[fields.desc])))
)
.data('json-obj', this);
if (selectedObj != null && selectedObj.id == item.id) {
$select.find('input[type=checkbox]').attr('checked', 'checked');
if (selectedObjNonEditable) {
$select.find('input[type=checkbox]').attr('disabled', 'disabled');
$select.find('input[type=checkbox]').clone().attr({
type: 'hidden',
disabled: false
}).appendTo($selects);
}
}
$selects.append($select);
if (item._singleSelect) {
$select.addClass('single-select');
}
if (options.secondary) {
var $secondary = $('<div>').addClass('secondary-input').append(
$('<input>')
.attr({
type: options.secondary.type,
name: options.secondary.name,
'wizard-field': options.secondary['wizard-field']
})
.val(id)
.click(function() {
var $checkbox = $(this).closest('.select').find('input[type=checkbox]');
if (!$checkbox.is(':checked')) {
$checkbox.attr('checked', true);
}
if ($(this).closest('.select-container').hasClass('single-select')) {
$(this).closest('.select').siblings().find('input[type=checkbox]')
.attr('checked', false);
}
if ($select.hasClass('single-select')) {
$select.siblings('.single-select:visible').find('input[type=checkbox]')
.attr('checked', false);
}
})
.after(
$('<div>').addClass('name').html(options.secondary.desc)
)
).appendTo($select);
}
});
cloudStack.evenOdd($selects, 'div.select', {
even: function($elem) {
$elem.addClass('even');
},
odd: function($elem) {
$elem.addClass('odd');
}
});
return $selects.children();
};
var dataProvider = function(step, providerArgs, callback) {
// Call appropriate data provider
args.steps[step - 1]($.extend(providerArgs, {
currentData: cloudStack.serializeForm($form),
initArgs: args,
context: context
}));
};
var dataGenerators = {
setup: function($step, formData) {
var originalValues = function(formData, initialValue) {
var selectedValue = formData.zoneid || initialValue;
$step.find('select').val(selectedValue);
$step.find('input[type=radio]').filter(function() {
return $(this).val() == formData['select-template'];
}).click();
};
return {
response: {
success: function(args) {
// Zones
var initialValue = '';
$(args.data.zones).each(function( index ) {
if(index == 0){
initialValue = this.id;
}
$step.find('.select-zone select').append(
$('<option>')
.attr({
value: this.id,
'wizard-field': 'zone'
})
.html(this.name)
);
});
originalValues(formData, initialValue);
}
}
};
},
'select-iso': function($step, formData) {
$step.find('.section.custom-size').hide();
var originalValues = function(formData) {
var $inputs = $step.find('.wizard-step-conditional:visible')
.find('input[type=radio]');
var $selected = $inputs.filter(function() {
return $(this).val() == formData.templateid;
});
if (!$selected.length) {
$inputs.filter(':first').click();
} else {
$selected.click();
}
$step.find('select[name=hypervisorid]:visible').val(
formData.hypervisorid
);
};
$step.find('.wizard-step-conditional').hide();
return {
response: {
success: function(args) {
if (formData['select-template']) {
$step.find('.wizard-step-conditional').filter(function() {
return $(this).hasClass(formData['select-template']);
}).show();
} else {
$step.find('.select-iso').show();
}
var makeIsos = function(type, append) {
var $selects = makeSelects('templateid', args.data.templates[type], {
name: 'name',
desc: 'displaytext',
id: 'id'
}, {
'wizard-field': 'template'
});
var $templateHypervisor = $step.find('input[type=hidden][wizard-field=hypervisor]');
// Get hypervisor from template
if (type == 'featuredtemplates' || type == 'communitytemplates' || type == 'mytemplates' || type == 'sharedtemplates') {
$selects.each(function() {
var $select = $(this);
var template = $.grep(args.data.templates[type], function(tmpl, v) {
return tmpl.id == $select.find('input').val();
})[0];
$select.change(function() {
$templateHypervisor
.attr('disabled', false)
.val(template.hypervisor);
});
});
} else {
$templateHypervisor.attr('disabled', 'disabled');
}
if (type == 'featuredisos' || type == 'communityisos' || type == 'myisos' || type == 'sharedisos') {
// Create hypervisor select
$selects.find('input').bind('click', function() {
var $select = $(this).closest('.select');
//$select.siblings().removeClass('selected').find('.hypervisor').remove(); //SelectISO has 3 tabs now. This line only remove hypervisor div in the same tab, not enough. The following 3 lines will remove hypervisor div in all of 3 tabs.
$("#instance-wizard-featured-isos .select-container div.selected").removeClass('selected').find('div.hypervisor').remove();
$("#instance-wizard-community-isos .select-container div.selected").removeClass('selected').find('div.hypervisor').remove();
$("#instance-wizard-my-isos .select-container div.selected").removeClass('selected').find('div.hypervisor').remove();
$("#instance-wizard-shared-isos .select-container div.selected").removeClass('selected').find('div.hypervisor').remove();
$select.addClass('selected').append(
$('<div>').addClass('hypervisor')
.append($('<label>').html(_l('label.hypervisor') + ':'))
.append($('<select>').attr({
name: 'hypervisorid'
}))
);
// Get hypervisor data
$(args.data.hypervisors).each(function() {
$select.find('select').append(
$('<option>').attr({
value: this[args.hypervisor.idField],
'wizard-field': 'hypervisor'
})
.html(this[args.hypervisor.nameField])
);
});
});
}
append($selects);
};
// Featured ISOs
$(
[
// Templates
['featuredtemplates', 'instance-wizard-featured-templates'],
['communitytemplates', 'instance-wizard-community-templates'],
['mytemplates', 'instance-wizard-my-templates'],
['sharedtemplates', 'instance-wizard-shared-templates'],
// ISOs
['featuredisos', 'instance-wizard-featured-isos'],
['communityisos', 'instance-wizard-community-isos'],
['myisos', 'instance-wizard-my-isos'],
['sharedisos', 'instance-wizard-shared-isos']
//['isos', 'instance-wizard-all-isos']
]
).each(function() {
var item = this;
var $selectContainer = $wizard.find('#' + item[1]).find('.select-container');
makeIsos(item[0], function($elem) {
$selectContainer.append($elem);
});
});
var custom = args.customHidden({
context: context,
data: args.data
});
$step.find('.custom-size-label').remove();
if (custom) {
$step.find('.section.custom-size').hide();
$step.removeClass('custom-slider-container');
}
$step.find('input[type=radio]').bind('change', function() {
var $target = $(this);
var val = $target.val();
var item = null;
if (item == null && args.data.templates.featuredtemplates != undefined) {
item = $.grep(args.data.templates.featuredtemplates, function(elem) {
return elem.id == val;
})[0];
}
if (item == null && args.data.templates.communitytemplates != undefined) {
item = $.grep(args.data.templates.communitytemplates, function(elem) {
return elem.id == val;
})[0];
}
if (item == null && args.data.templates.mytemplates!=undefined) {
item = $.grep(args.data.templates.mytemplates, function(elem) {
return elem.id == val;
})[0];
}
if (item == null && args.data.templates.sharedtemplates!=undefined) {
item = $.grep(args.data.templates.sharedtemplates, function(elem) {
return elem.id == val;
})[0];
}
if (!item) return true;
var hypervisor = item['hypervisor'];
if (hypervisor == 'KVM' || hypervisor == 'XenServer' || hypervisor == 'VMware') {
$step.find('.section.custom-size').show();
$step.addClass('custom-slider-container');
} else {
$step.find('.section.custom-size').hide();
$step.removeClass('custom-slider-container');
}
return true;
});
originalValues(formData);
}
}
};
},
'service-offering': function($step, formData) {
var originalValues = function(formData) {
if (formData.serviceofferingid) {
$step.find('input[type=radio]').filter(function() {
return $(this).val() == formData.serviceofferingid;
}).click();
} else {
$step.find('input[type=radio]:first').click();
}
};
return {
response: {
success: function(args) {
$step.find('.content .select-container').append(
makeSelects('serviceofferingid', args.data.serviceOfferings, {
name: 'name',
desc: 'displaytext',
id: 'id'
}, {
'wizard-field': 'service-offering'
})
);
$step.find('input[type=radio]').bind('change', function() {
var $target = $(this);
var val = $target.val();
var item = $.grep(args.data.serviceOfferings, function(elem) {
return elem.id == val;
})[0];
if (!item) return true;
var custom = item[args.customFlag];
if (custom) {
// contains min/max CPU and Memory values
$step.addClass('custom-size');
var offeringDetails = item['serviceofferingdetails'];
var offeringCpuSpeed = item['cpuspeed'];
$step.find('.custom-no-limits').hide();
$step.find('.custom-slider-container').hide();
var minCpuNumber = 0, maxCpuNumber = 0, minMemory = 0, maxMemory = 0;
if (offeringDetails){
minCpuNumber = offeringDetails['mincpunumber'];
maxCpuNumber = offeringDetails['maxcpunumber'];
minMemory = offeringDetails['minmemory'];
maxMemory = offeringDetails['maxmemory'];
}
if (minCpuNumber > 0 && maxCpuNumber > 0 && minMemory > 0 && maxMemory > 0) {
$step.find('.custom-slider-container.slider-cpu-speed input[type=text]').val(parseInt(offeringCpuSpeed));
$step.find('.custom-slider-container').show();
var setupSlider = function(sliderClassName, minVal, maxVal) {
$step.find('.custom-slider-container .' + sliderClassName + ' .size.min span').html(minVal);
$step.find('.custom-slider-container .' + sliderClassName + ' input[type=text]').val(minVal);
$step.find('.custom-slider-container .' + sliderClassName + ' .size.max span').html(maxVal);
$step.find('.custom-slider-container .' + sliderClassName + ' .slider').each(function() {
var $slider = $(this);
$slider.slider({
min: parseInt(minVal),
max: parseInt(maxVal),
slide: function(event, ui) {
$slider.closest('.section.custom-size .' + sliderClassName + '').find('input[type=text]').val(ui.value);
$step.find('span.custom-slider-container .' + sliderClassName + '').html(ui.value);
}
});
});
$step.find('.custom-slider-container .' + sliderClassName + ' input[type=text]').bind('change', function() {
var val = $step.find('.custom-slider-container .' + sliderClassName + ' input[type=text]').val();
if (val < minVal || val > maxVal) {
cloudStack.dialog.notice({ message: $.validator.format(_l('message.validate.range'), [minVal, maxVal]) });
}
if (val < minVal) {
val = minVal;
$step.find('.custom-slider-container .' + sliderClassName + ' input[type=text]').val(val);
}
if(val > maxVal) {
val = maxVal;
$step.find('.custom-slider-container .' + sliderClassName + ' input[type=text]').val(val);
}
$step.find('span.custom-slider-container .' + sliderClassName).html(_s(val));
});
}
setupSlider('slider-cpu-cores', minCpuNumber, maxCpuNumber);
setupSlider('slider-memory-mb', minMemory, maxMemory);
} else {
$step.find('.custom-slider-container.slider-cpu-speed.slider-compute-cpu-speed').val(0);
$step.find('.custom-no-limits').show();
}
} else {
$step.find('.custom-no-limits').hide();
$step.find('.custom-slider-container').hide();
$step.removeClass('custom-size');
}
var customIops = item[args.customIopsFlag];
if (customIops && args.canShowCustomIops) {
$step.addClass('custom-iops');
} else {
$step.removeClass('custom-iops');
}
return true;
});
originalValues(formData);
}
}
};
},
'data-disk-offering': function($step, formData) {
var originalValues = function(formData) {
var $targetInput = $step.find('input[type=radio]').filter(function() {
return $(this).val() == formData.diskofferingid;
}).click();
if (!$targetInput.length) {
$step.find('input[type=radio]:visible').filter(':first').click();
}
};
$step.find('.section.custom-size').hide();
return {
response: {
success: function(args) {
var multiDisk = args.multiDisk;
$step.find('.multi-disk-select-container').remove();
$step.removeClass('custom-slider-container');
$step.find('.main-desc, p.no-datadisk').remove();
if (!multiDisk){
if (args.required) {
$step.find('.section.no-thanks')
.hide();
$step.addClass('required');
} else {
$step.find('.section.no-thanks')
.show();
$step.removeClass('required');
}
} else {
$step.find('.section.no-thanks').hide();
$step.addClass('required');
}
var $selectContainer = $step.find('.content .select-container:not(.multi-disk)');
if (multiDisk) { // Render as multiple groups for each disk
if (multiDisk[0].id == "none"){
$step.find('.select-container').append(
$('<p>').addClass('no-datadisk').html(_l('message.no.datadisk'))
);
return;
}
var $multiDiskSelect = $('<div>').addClass('multi-disk-select-container');
$(multiDisk).map(function(index, disk) {
var array_do = [];
$.each(args.data.diskOfferings, function( key, value ) {
if (value){
if (value.disksize >= disk.size && value.name != "Custom"){
array_do.push(value);
}
}
})
var $group = $('<div>').addClass('disk-select-group');
var $header = $('<div>').addClass('disk-select-header').append(
$('<div>').addClass('title').html(disk.label)
).appendTo($group);
var $checkbox = $('<input>').addClass('multi-disk-select')
.attr({
type: 'checkbox',
'disk-id': disk.id
})
.prependTo($header);
var $multiSelectContainer = $selectContainer.clone().append(
makeSelects('diskofferingid.' + disk.id, array_do, {
id: 'id',
name: 'name',
desc: 'displaytext'
}, {
'wizard-field': 'disk-offering'
})
).appendTo($group).addClass('multi-disk');
$group.appendTo($multiDiskSelect);
$group.data('json-obj', disk);
// Show-hide disk group selects
$checkbox.click(function() {
$group.toggleClass('selected');
$group.find('.select:first input[type=radio]').click();
if (!$multiDiskSelect.find('input[type=checkbox]:checked').length) {
$step.find('.no-thanks input[type=radio]').click();
} else {
$step.find('.no-thanks input[type=radio]').attr('checked', false);
}
});
// Add custom disk size box
$step.find('.section.custom-size').clone().hide().appendTo($group);
});
$multiDiskSelect.insertAfter($selectContainer);
$selectContainer.hide();
// Fix issue with containers always showing after reload
$multiDiskSelect.find('.select-container').attr('style', null);
} else {
$selectContainer.show();
$step.find('.content .select-container').append(
makeSelects('diskofferingid', args.data.diskOfferings, {
id: 'id',
name: 'name',
desc: 'displaytext'
}, {
'wizard-field': 'disk-offering'
})
);
}
$step.find('input[type=radio]').bind('change', function() {
var $target = $(this);
var val = $target.val();
var item = $.grep(args.data.diskOfferings, function(elem) {
return elem.id == val;
})[0];
var isMultiDisk = $step.find('.multi-disk-select').length;
// Uncheck any multi-select groups
if ($target.closest('.no-thanks').length && isMultiDisk) {
$step.find('.disk-select-group input[type=checkbox]:checked').click();
$(this).attr('checked', true);
return true;
}
if (!item) {
if (isMultiDisk) {
$(this).closest('.disk-select-group .section.custom-size').hide();
$(this).closest('.disk-select-group').removeClass('custom-size');
} else {
// handle removal of custom size controls
$step.find('.section.custom-size').hide();
$step.removeClass('custom-slider-container');
// handle removal of custom IOPS controls
$step.removeClass('custom-iops-do');
}
return true;
}
var custom = item[args.customFlag];
if (!isMultiDisk) $step.find('.custom-size-label').remove();
if (custom && !isMultiDisk) {
$target.parent().find('.name')
.append(
$('<span>').addClass('custom-size-label')
.append(': ')
.append(
$('<span>').addClass('custom-slider-container').html(
$step.find('.custom-size input[name=size]').val()
)
)
.append(' GB')
);
$target.parent().find('.select-desc .desc')
.append(
$('<span>').addClass('custom-size-label')
.append(', ')
.append(
$('<span>').addClass('custom-slider-container').html(
$step.find('.custom-size input[name=size]').val()
)
)
.append(' GB')
);
$step.find('.section.custom-size').show();
$step.addClass('custom-slider-container');
$target.closest('.select-container').scrollTop(
$target.position().top
);
} else if (custom && isMultiDisk) {
$(this).closest('.disk-select-group').addClass('custom-size');
} else {
if (isMultiDisk) {
$(this).closest('.disk-select-group').removeClass('custom-size');
} else {
$step.find('.section.custom-size').hide();
$step.removeClass('custom-slider-container');
}
}
var customIops = item[args.customIopsDoFlag];
if (customIops) {
$step.addClass('custom-iops-do');
} else {
$step.removeClass('custom-iops-do');
}
return true;
});
originalValues(formData);
}
}
};
},
'affinity': function($step, formData) {
return {
response: {
success: function(args) {
// Cleanup
$step.find('.main-desc, p.no-affinity-groups').remove();
if (args.data.affinityGroups && args.data.affinityGroups.length) {
$step.prepend(
$('<div>').addClass('main-desc').append(
$('<p>').html(_l('message.select.affinity.groups'))
)
);
$step.find('.select-container').append(
makeSelects(
'affinity-groups',
args.data.affinityGroups, {
name: 'name',
desc: 'description',
id: 'id'
}, {
type: 'checkbox',
'wizard-field': 'affinity-groups'
},
args.data.selectedObj,
args.data.selectedObjNonEditable
)
);
} else {
$step.find('.select-container').append(
$('<p>').addClass('no-affinity-groups').html(_l('message.no.affinity.groups'))
);
}
}
}
};
},
'sshkeyPairs': function($step, formData) {
var originalValues = function(formData) {
if (formData.sshkeypair) {
$step.find('input[type=radio]').filter(function() {
return $(this).val() == formData.sshkeypair;
}).click();
} else {
$step.find('input[type=radio]:first').click();
}
};
return {
response: {
success: function(args) {
$step.find('.main-desc, p.no-sshkey-pairs').remove();
if (args.data.sshkeyPairs && args.data.sshkeyPairs.length) {
$step.prepend(
$('<div>').addClass('main-desc').append(
$('<p>').html(_l('message.please.select.ssh.key.pair.use.with.this.vm'))
)
);
$step.find('.section.no-thanks').show();
$step.find('.select-container').append(
makeSelects(
'sshkeypair',
args.data.sshkeyPairs, {
name: 'name',
id: 'name'
}, {
'wizard-field': 'sshkey-pairs'
}
)
);
originalValues(formData); // if we can select only one.
} else {
$step.find('.section.no-thanks').hide();
$step.find('.select-container').append(
$('<p>').addClass('no-sshkey-pairs').html(_l('You do not have any ssh key pairs. Please continue to the next step.'))
);
}
}
}
};
},
'network': function($step, formData) {
var showAddNetwork = true;
var checkShowAddNetwork = function($newNetwork) {
if (!showAddNetwork) {
$newNetwork.hide();
} else {
$newNetwork.show();
}
};
var originalValues = function(formData) {
// Default networks
$step.find('input[type=radio]').filter(function() {
return $(this).val() == formData['defaultNetwork'];
}).click();
// Optional networks
var selectedOptionalNetworks = [];
if ($.isArray(formData['shared-networks']) != -1) {
$(formData['shared-networks']).each(function() {
selectedOptionalNetworks.push(this);
});
} else {
selectedOptionalNetworks.push(formData['shared-networks']);
}
var $checkboxes = $step.find('input[name=shared-networks]');
$(selectedOptionalNetworks).each(function() {
var networkID = this;
$checkboxes.filter(function() {
return $(this).val() == networkID;
}).attr('checked', 'checked');
});
};
var $newNetwork = $step.find('.new-network');
var $newNetworkCheckbox = $newNetwork.find('input[type=checkbox]');
// Setup new network field
$newNetworkCheckbox.unbind('click');
$newNetworkCheckbox.click(function() {
$newNetwork.toggleClass('unselected');
// Select another default if hiding field
if ($newNetwork.hasClass('unselected')) {
$step.find('input[type=radio]:visible:first').click();
} else {
$newNetwork.find('input[type=radio]').click();
}
});
setTimeout(function() {
var $checkbox = $step.find('.new-network input[type=checkbox]');
var $newNetwork = $checkbox.closest('.new-network');
if ($step.find('.select.my-networks .select-container .select:visible').length) {
$checkbox.attr('checked', false);
$newNetwork.addClass('unselected');
} else {
$newNetwork.find('input[name=defaultNetwork]').filter('[value=new-network]').click();
}
$checkbox.change();
});
// Show relevant conditional sub-step if present
$step.find('.wizard-step-conditional').hide();
if ($.isFunction(args.showAddNetwork)) {
showAddNetwork = args.showAddNetwork({
data: formData,
context: context
});
}
// Filter network list by VPC ID
var filterNetworkList = function(vpcID) {
var $selects = $step.find('.my-networks .select-container .select');
var $visibleSelects = $($.grep($selects, function(select) {
var $select = $(select);
return args.vpcFilter($select.data('json-obj'), vpcID);
}));
var $addNetworkForm = $step.find('.select.new-network');
var $addNewNetworkCheck = $addNetworkForm.find('input[name=new-network]');
// VPC networks cannot be created via instance wizard
if (vpcID != -1) {
$step.find('.my-networks .select-container').addClass('single-select');
$addNetworkForm.hide();
if ($addNewNetworkCheck.is(':checked')) {
$addNewNetworkCheck.click();
$addNewNetworkCheck.attr('checked', false);
}
} else {
$step.find('.my-networks .select-container').removeClass('single-select');
$addNetworkForm.show();
checkShowAddNetwork($addNetworkForm);
}
$selects.find('input[type=checkbox]').attr('checked', false);
$selects.hide();
$visibleSelects.show();
// Select first visible item by default
$visibleSelects.filter(':first')
.find('input[type=radio]')
.click();
cloudStack.evenOdd($visibleSelects, 'div.select', {
even: function($elem) {
$elem.removeClass('odd');
$elem.addClass('even');
},
odd: function($elem) {
$elem.removeClass('even');
$elem.addClass('odd');
}
});
};
var $vpcSelect = $step.find('select[name=vpc-filter]');
$vpcSelect.unbind('change');
$vpcSelect.change(function() {
filterNetworkList($vpcSelect.val());
});
return {
response: {
success: function(args) {
var vpcs = args.data.vpcs;
var addClass = args.addClass;
var removeClass = args.removeClass;
// Populate VPC drop-down
$vpcSelect.html('');
$(vpcs).map(function(index, vpc) {
var $option = $('<option>');
var id = vpc.id;
var description = vpc.name;
$option.attr('value', id);
$option.html(description);
$option.appendTo($vpcSelect);
});
// 'No VPC' option
$('<option>').attr('value', '-1').html(_l('ui.listView.filters.all')).prependTo($vpcSelect);
$vpcSelect.val(-1);
// Populate network offering drop-down
$(args.data.networkOfferings).each(function() {
$('<option>')
.val(this.id)
.html(this.name)
.appendTo($newNetwork.find('select'));
});
if (args.type) {
$step.find('.wizard-step-conditional').filter(function() {
return $(this).hasClass(args.type);
}).show();
} else {
$step.find('.select-network').show();
}
// My networks
$step.find('.my-networks .select-container').append(
makeSelects('my-networks', args.data.networkObjs, {
name: 'name',
desc: 'type',
id: 'id'
}, {
type: 'checkbox',
'wizard-field': 'my-networks',
secondary: {
desc: 'Default',
name: 'defaultNetwork',
type: 'radio',
'wizard-field': 'default-network'
}
})
);
// Add IP/advanced option fields
$step.find('.my-networks .select-container .select, .select.new-network .select').each(function () {
var $select = $(this);
var $advancedLink = $('<div>').addClass('advanced-options hide-if-unselected');
var $specifyIpField = $('<div>').addClass('specify-ip hide-if-unselected').append(
$('<label>').html(_l('label.ip.address')),
$('<input>').attr({ type: 'text' })
);
// Cleanup
if ($select.closest('.new-network').length) {
$select.find('.advanced-options, .specify-ip').remove();
}
$select.append($advancedLink, $specifyIpField);
$advancedLink.click(function() {
$select.toggleClass('advanced');
});
});
// Show non-VPC networks by default
filterNetworkList(-1);
// Security groups (alt. page)
var $sgSelects = makeSelects('security-groups', args.data.securityGroups, {
name: 'name',
desc: 'description',
id: 'id'
}, {
type: 'checkbox',
'wizard-field': 'security-groups'
});
$step.find('.security-groups .select-container').append($sgSelects);
//If there is only one security group and the only one is 'default', make it selected by default
if ($sgSelects.length == 1) {
var $firstCheckbox = $sgSelects.eq(0);
if ($firstCheckbox.find('div .name').text() == 'default') {
$firstCheckbox.find('input:checkbox').click();
}
}
originalValues(formData);
checkShowAddNetwork($newNetwork);
}
}
};
},
'review': function($step, formData) {
$step.find('[wizard-field]').each(function() {
var field = $(this).attr('wizard-field');
var fieldName;
var $input = $wizard.find('[wizard-field=' + field + ']').filter(function() {
return ($(this).is(':selected') ||
$(this).is(':checked') ||
$(this).attr('type') == 'hidden') &&
$(this).is(':not(:disabled)');
});
if ($input.is('option')) {
fieldName = $input.html();
} else if ($input.is('input[type=radio]')) {
// Choosen New network as default
if ($input.parents('div.new-network').length) {
fieldName = $input.closest('div.new-network').find('input[name="new-network-name"]').val();
// Choosen Network from existed
} else if ($input.parents('div.my-networks').length) {
fieldName = $input.closest('div.select').find('.select-desc .name').html();
} else {
fieldName = $input.parent().find('.select-desc .name').html();
}
} else if ($input.eq(0).is('input[type=checkbox]')) {
fieldName = '';
$input.each(function(index) {
if (index != 0) fieldName += '<br />';
fieldName += $(this).next('div.select-desc').find('.name').html();
});
} else if ($input.is('input[type=hidden]')) {
fieldName = $input.val();
}
if (fieldName) {
$(this).html(fieldName);
} else {
$(this).html('(' + _l('label.none') + ')');
}
var conditionalFieldFrom = $(this).attr('conditional-field');
if (conditionalFieldFrom) {
if ($wizard.find('.' + conditionalFieldFrom).css('display') == 'block') {
$(this).closest('div.select').show();
} else {
$(this).closest('div.select').hide();
}
}
});
}
};
$wizard.bind('cloudStack.instanceWizard.showStep', function(e, args) {
showStep(args.index, { refresh: true });
});
// Go to specified step in wizard,
// updating nav items and diagram
var showStep = function(index, options) {
if (!options) options = {};
var targetIndex = index - 1;
if (index <= 1) targetIndex = 0;
if (targetIndex == $steps.length) {
completeAction();
return;
}
var $targetStep = $($steps.hide()[targetIndex]).show();
var stepID = $targetStep.attr('wizard-step-id');
var formData = cloudStack.serializeForm($form);
if (options.refresh) {
$targetStep.removeClass('loaded');
}
if (!$targetStep.hasClass('loaded')) {
// Remove previous content
if (!$targetStep.hasClass('review')) { // Review row content is not autogenerated
$targetStep.find('.select-container:not(.fixed) div, option:not(:disabled)').remove();
}
dataProvider(
index,
dataGenerators[stepID](
$targetStep,
formData
)
);
if (!$targetStep.hasClass('repeat') && !$targetStep.hasClass('always-load')) $targetStep.addClass('loaded');
}
// Show launch vm button if last step
var $nextButton = $wizard.find('.button.next');
$nextButton.find('span').html(_l('label.next'));
$nextButton.removeClass('final');
if ($targetStep.hasClass('review')) {
$nextButton.find('span').html(_l('label.launch.vm'));
$nextButton.addClass('final');
}
// Hide previous button on first step
var $previousButton = $wizard.find('.button.previous');
if (index == 1) $previousButton.hide();
else $previousButton.show();
// Update progress bar
var $targetProgress = $progress.removeClass('active').filter(function() {
return $(this).index() <= targetIndex;
}).toggleClass('active');
// Update diagram; show/hide as necessary
$diagramParts.filter(function() {
return $(this).index() <= targetIndex;
}).fadeIn('slow');
$diagramParts.filter(function() {
return $(this).index() > targetIndex;
}).fadeOut('slow');
setTimeout(function() {
if (!$targetStep.find('input[type=radio]:checked').length) {
$targetStep.find('input[type=radio]:first').click();
}
}, 50);
};
// Events
$wizard.click(function(event) {
var $target = $(event.target);
var $activeStep = $form.find('.step:visible');
// Next button
if ($target.closest('div.button.next').length) {
//step 2 - select template/ISO
if($activeStep.hasClass('select-iso')) {
if ($activeStep.find('.content:visible input:checked').length == 0) {
cloudStack.dialog.notice({
message: 'message.step.1.continue'
});
return false;
}
$(window).trigger("cloudStack.module.instanceWizard.clickNextButton", {
$form: $form,
currentStep: 2
});
}
//step 6 - select network
if ($activeStep.find('.wizard-step-conditional.select-network:visible').length > 0) {
var data = $activeStep.data('my-networks');
if (!data) {
$activeStep.closest('form').data('my-networks', cloudStack.serializeForm(
$activeStep.closest('form')
)['my-networks']);
}
if ($activeStep.find('input[type=checkbox]:checked').length == 0) { //if no checkbox is checked
cloudStack.dialog.notice({
message: 'message.step.4.continue'
});
return false;
}
if ($activeStep.hasClass('next-use-security-groups')) {
var advSGFilter = args.advSGFilter({
data: cloudStack.serializeForm($form)
});
if (advSGFilter == 0) { //when total number of selected sg networks is 0, then 'Select Security Group' is skipped, go to step 6 directly
showStep(6);
} else { //when total number of selected sg networks > 0
if ($activeStep.find('input[type=checkbox]:checked').length > 1) { //when total number of selected networks > 1
cloudStack.dialog.notice({
message: "Can't create a vm with multiple networks one of which is Security Group enabled"
});
return false;
}
}
}
}
//step 6 - review (spcifiy displyname, group as well)
if ($activeStep.hasClass('review')) {
if ($activeStep.find('input[name=displayname]').length > 0 && $activeStep.find('input[name=displayname]').val().length > 0) {
//validate
var b = cloudStack.validate.vmHostName($activeStep.find('input[name=displayname]').val());
if (b == false)
return false;
}
}
if (!$form.valid()) {
if ($form.find('input.error:visible, select.error:visible').length) {
return false;
}
}
if ($activeStep.hasClass('repeat')) {
showStep($steps.filter(':visible').index() + 1);
} else {
showStep($steps.filter(':visible').index() + 2);
}
return false;
}
// Previous button
if ($target.closest('div.button.previous').length) {
var $step = $steps.filter(':visible');
var $networkStep = $steps.filter('.network');
var index = $step.index();
$networkStep.removeClass('next-use-security-groups');
if (index) {
if (index == $steps.length - 1 && $networkStep.hasClass('next-use-security-groups')) {
showStep(5);
} else if ($activeStep.find('.select-security-group:visible').length &&
$activeStep.find('.select-network.no-add-network').length) {
showStep(5);
} else {
showStep(index);
}
}
return false;
}
// Close button
if ($target.closest('div.button.cancel').length) {
close();
return false;
}
// Edit link
if ($target.closest('div.edit').length) {
var $edit = $target.closest('div.edit');
showStep($edit.find('a').attr('href'));
return false;
}
return true;
});
showStep(1);
$wizard.bind('change', function(event) {
var $target = $(event.target);
var $step = $target.closest('.step');
var $futureSteps = $step.siblings().filter(function() {
return $(this).index() > $step.index();
});
// Reset all fields in futher steps
$futureSteps.removeClass('loaded');
});
var minCustomDiskSize = args.minDiskOfferingSize ?
args.minDiskOfferingSize() : 1;
var maxCustomDiskSize = args.maxDiskOfferingSize ?
args.maxDiskOfferingSize() : 100;
// Setup tabs and slider
$wizard.find('.section.custom-size.custom-slider-container .size.min span').html(minCustomDiskSize);
$wizard.find('.section.custom-size.custom-slider-container input[type=text]').val(minCustomDiskSize);
$wizard.find('.section.custom-size.custom-slider-container .size.max span').html(maxCustomDiskSize);
$wizard.find('.tab-view').tabs();
$wizard.find('.slider').each(function() {
var $slider = $(this);
$slider.slider({
min: minCustomDiskSize,
max: maxCustomDiskSize,
start: function(event) {
$slider.closest('.section.custom-size').find('input[type=radio]').click();
},
slide: function(event, ui) {
$slider.closest('.section.custom-size').find('input[type=text]').val(
ui.value
);
$slider.closest('.step').find('span.custom-slider-container').html(
ui.value
);
}
});
});
$wizard.find('div.data-disk-offering div.custom-size input[type=text]').bind('change', function() {
var old = $wizard.find('div.data-disk-offering div.custom-size input[type=text]').val();
$wizard.find('div.data-disk-offering span.custom-slider-container').html(_s(old));
});
var wizardDialog = $wizard.dialog({
title: _l('label.vm.add'),
width: 896,
minHeight: 600,
height: 'auto',
closeOnEscape: false,
modal: true
});
var wizardDialogDiv = wizardDialog.closest('.ui-dialog');
$('button.ui-dialog-titlebar-close').remove();
return wizardDialogDiv.overlay();
};
instanceWizard(args);
};
};
})(jQuery, cloudStack);