[PATCH v2] [Kimchi] Create new volume and attach to VM

V2: Add ".img" to name of new volume being created V1: This is the initial checkin for creating a new volume and attaching to VM when editing a guest. Not sure of all the supported cases, but tried default pool and qcow2 format per the example provided in the back-end patch and it was successful. Known issue - which currently exists in the code already in master - code is broken when VM is running. This patch does not address that. It will get addressed separately. Socorro Stoppler (1): Create new volume and attach to VM Add ".img" to name of new volume ui/js/src/kimchi.guest_storage_add.main.js | 320 ++++++++++++++++++++++++----- ui/pages/guest-storage-add.html.tmpl | 58 ++++-- 2 files changed, 315 insertions(+), 63 deletions(-) -- 1.9.1

Signed-off-by: Socorro Stoppler <socorro@linux.vnet.ibm.com> --- ui/js/src/kimchi.guest_storage_add.main.js | 320 ++++++++++++++++++++++++----- ui/pages/guest-storage-add.html.tmpl | 58 ++++-- 2 files changed, 315 insertions(+), 63 deletions(-) diff --git a/ui/js/src/kimchi.guest_storage_add.main.js b/ui/js/src/kimchi.guest_storage_add.main.js index 6e1926b..9c71486 100644 --- a/ui/js/src/kimchi.guest_storage_add.main.js +++ b/ui/js/src/kimchi.guest_storage_add.main.js @@ -15,6 +15,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +kimchi.switchPage = function(fromPageId, toPageId, direction) { + $('.tab-content').css('overflow', 'hidden'); + direction = direction || 'left'; + var toLeftBegin; + var fromLeftEnd; + if ('left' === direction) { + toLeftBegin = '100%'; + fromLeftEnd = '-100%'; + } else if ('right' === direction) { + toLeftBegin = '-100%'; + fromLeftEnd = '100%'; + } + var formPage = $('#' + fromPageId); + var toPage = $('#' + toPageId); + toPage.css({ + left: toLeftBegin + }); + formPage.animate({ + left: fromLeftEnd, + opacity: 0.1 + }, 400, function() { + $('.tab-content').css('overflow', 'visible'); + }); + toPage.animate({ + left: '0', + opacity: 1 + }, 400, function() { + $('.tab-content').css('overflow', 'visible'); + }); +}; + kimchi.guest_storage_add_main = function() { var types = [{ label: 'cdrom', @@ -35,9 +66,27 @@ kimchi.guest_storage_add_main = function() { var pathTextbox = $('input[name="path"]', storageAddForm); var poolTextbox = $('select#guest-disk-pool', storageAddForm); var volTextbox = $('select#guest-disk-vol', storageAddForm); + var newPoolTextbox = $('select#guest-disk-pool-new', storageAddForm); + var capacityTextbox = $('input[name="capacity"]', storageAddForm); + var formatTextbox = $('select#guest-disk-format-new', storageAddForm); var selectStorageTypeHTML = ''; var selectStoragePoolHTML = ''; var selectStorageVolHTML = ''; + var rbExisting = 'false'; + + var getFormatList = function() { + var format = ["bochs", "cloop", "cow", "dmg", "qcow", "qcow2", "qed", "raw", "vmdk", "vpc"]; + var selectFormatHTML = ''; + var i; + for (i = 0; i < format.length; i++) { + selectFormatHTML += '<option value="'+ format[i] + '">' + format[i] + '</option>'; + } + formatTextbox.empty(); + formatTextbox.append(selectFormatHTML); + $(formatTextbox).change(); + formatTextbox.selectpicker(); + $('.selectpicker').selectpicker('refresh'); + }; typeTextbox.change(function() { var pathObject = {'cdrom': ".path-section", 'disk': '.volume-section'}; @@ -45,41 +94,82 @@ kimchi.guest_storage_add_main = function() { $.each(pathObject, function(type, value) { if(selectType === type){ $(value).removeClass('hidden'); + } else if ((selectType === null) && (type === 'disk')) { + $(value).removeClass('hidden'); } else { $(value).addClass('hidden'); } }); - if ($(".path-section").hasClass('hidden')) { - $(poolTextbox).val('default'); - $(poolTextbox).change(); $(pathTextbox).val(""); - } - else { + if ($('#new-disk').checked) { + $('#existing-disk-box').addClass('hidden'); + $(newPoolTextbox).val('default'); + $(newPoolTextbox).change(); + } else if ($('#existing-disk').checked) { + $('#new-disk-box').addClass('hidden'); + $(poolTextbox).val('default'); + $(poolTextbox).change(); + } else { + if (rbExisting === 'true') { + $('#new-disk-box').addClass('hidden'); + } else { + $('#existing-disk-box').addClass('hidden'); + } + } + } else { $(poolTextbox).val(""); $(volTextbox).val(""); + $(newPoolTextbox).val(""); + $(capacityTextbox).val(""); + $(formatTextbox).val(""); } $('.selectpicker').selectpicker('refresh'); }); - kimchi.listStoragePools(function(result) { - var options = []; - if (result && result.length) { - $.each(result, function(index, storagePool) { - if ((storagePool.state==="active") && (storagePool.type !== 'kimchi-iso')) { - options.push({ - label: storagePool.name, - value: storagePool.name - }); - selectStoragePoolHTML += '<option value="'+ storagePool.name + '">' + storagePool.name + '</option>'; + var getStoragePools = function(radioButton) { + kimchi.listStoragePools(function(result) { + var options = []; + selectStoragePoolHTML = ''; //reset string + if (result && result.length) { + $.each(result, function(index, storagePool) { + if (radioButton === 'existing') { + if ((storagePool.state==="active") && (storagePool.type !== 'kimchi-iso')) { + options.push({ + label: storagePool.name, + value: storagePool.name + }); + selectStoragePoolHTML += '<option value="'+ storagePool.name + '">' + storagePool.name + '</option>'; + } + } else { //new disk + if ((storagePool.type != 'iscsi') && (storagePool.type != 'scsi')) { + options.push({ + label: storagePool.name, + value: storagePool.name + }); + selectStoragePoolHTML += '<option value="'+ storagePool.name + '">' + storagePool.name + '</option>'; + } } - }); - poolTextbox.append(selectStoragePoolHTML); - poolTextbox.val(options[0].value); - poolTextbox.selectpicker(); - } - }); + if (radioButton === 'existing') { + poolTextbox.empty(); + poolTextbox.append(selectStoragePoolHTML); + $(poolTextbox).change(); + poolTextbox.selectpicker(); + $('.selectpicker').selectpicker('refresh'); + } else if (radioButton === 'new') { //new disk + newPoolTextbox.empty(); + newPoolTextbox.append(selectStoragePoolHTML); + $(newPoolTextbox).val(options[0].value); + newPoolTextbox.selectpicker(); + getFormatList(); + } + } + }); + }; + + //First time retrieving list of Storage Pools - defaulting to new disk + getStoragePools('new'); poolTextbox.change(function() { var options = []; @@ -109,13 +199,15 @@ kimchi.guest_storage_add_main = function() { $(volTextbox).prop('disabled',true); $(submitButton).prop('disabled', true); } - volTextbox.selectpicker(); - $('.selectpicker').selectpicker('refresh'); + } else { + $(volTextbox).prop('disabled',true); + $(submitButton).prop('disabled', true); } + volTextbox.selectpicker(); + $('.selectpicker').selectpicker('refresh'); }, null, false); }); - typeTextbox.change(function() { var pathObject = {'cdrom': ".path-section", 'disk': '.volume-section'}; var selectType = $(this).val(); @@ -128,6 +220,47 @@ kimchi.guest_storage_add_main = function() { }); }); + var currentPage = 'new-disk-box'; + $('#existing-disk').change(function() { + if (this.checked) { + rbExisting = 'true'; + if (currentPage === 'new-disk-box') { + kimchi.switchPage(currentPage, 'existing-disk-box', 'right'); + } + currentPage = 'existing-disk-box'; + $('#existing-disk-box').removeClass('hidden'); + $('#new-disk-box').addClass('hidden'); + $('#guest-storage-add-window .modal-body .template-pager').animate({ + height: "200px" + }, 300); + getStoragePools('existing'); + $(pathTextbox).val(""); + $(newPoolTextbox).val(""); + $(capacityTextbox).val(""); + $(formatTextbox).val(""); + } + }); + + $('#new-disk').change(function() { + if (this.checked) { + rbExisting = 'false'; + if (currentPage === 'existing-disk-box') { + kimchi.switchPage(currentPage, 'new-disk-box', 'right'); + } else if($(capacityTextbox).is(":visible") === false ) { + kimchi.switchPage(currentPage, 'new-disk-box', 'right'); + } + currentPage = 'new-disk-box'; + $('#existing-disk-box').addClass('hidden'); + $('#new-disk-box').removeClass('hidden'); + $('#guest-storage-add-window .modal-body .template-pager').animate({ + height: "300px" + }, 400); + $(pathTextbox).val(""); + $(poolTextbox).val(""); + $(volTextbox).val(""); + } + }); + if (kimchi.thisVMState === 'running') { types =typesRunning; $(typeTextbox).val('disk'); @@ -155,15 +288,104 @@ kimchi.guest_storage_add_main = function() { } }; + var onError = function(result) { + if(!result) { + return; + } + var msg = result['message'] || ( + result['responseJSON'] && result['responseJSON']['reason'] + ); + wok.message.error(msg); + }; + + var addStorage = function(settings) { + kimchi.addVMStorage(settings, function(result) { + wok.window.close(); + wok.topic('kimchi/vmCDROMAttached').publish({ + result: result + }); + }, function(result) { + var errText = result['reason'] || + result['responseJSON']['reason']; + wok.message.error(errText, '#alert-modal-container2'); + $.each([submitButton, pathTextbox, poolTextbox, volTextbox, newPoolTextbox, capacityTextbox, formatTextbox], function(i, c) { + $(c).prop('disabled', false); + }); + }); + } + + var createVol = function(settings, addVolSettings) { + kimchi.createVolumeWithCapacity('default', { + name: settings['vol'], + format: settings['format'], + capacity: settings['capacity'] + }, function(result) { + var taskId = result.id; + function monitorTask() { + kimchi.getTask(taskId, function(result) { + var status = result.status; + if (status === "finished") { + //Now add newly created volume to VM + addStorage(addVolSettings); + } else if (status === "running") { + setTimeout(monitorTask, 2000); + $(submitButton).prop('disabled', true); + } else if (status === "failed") { + var errText = result['reason'] || + result['responseJSON']['reason']; + $(submitButton).prop('disabled', true); + wok.message.error(errText, '#alert-modal-container2'); + } + }); + } + setTimeout(monitorTask, 2000); + }, onError); + }; + + var bNewDisk = 'false'; + var validateDisk = function(settings) { - if (settings['pool'] && settings['vol']){ - // Delete path property since it's not needed for disk - delete settings['path']; - return true; + // Determine whether it's existing disk or new disk + if($(capacityTextbox).is(":visible") === true ) { + bNewDisk = 'true'; } - else { - wok.message.error(i18n['KCHVMSTOR0002E'],'#alert-modal-container2'); - return false; + if (bNewDisk === 'true') { + if (settings['newpool'] && settings['capacity'] && settings['format']){ + //Change settings['newpool'] to settings['pool'] + settings['pool']=settings['newpool']; + var vmname = settings['vm']; + vmname = vmname + new Date().getTime(); + //Unique vol name to be created + settings['vol']=vmname + ".img"; + //This is all that is needed for attaching newly created volume to VM + var addVolSettings = { + vm: settings['vm'], + type: settings['type'], + vol: settings['vol'], + pool: settings['pool'] + }; + var sizeInMB = parseInt(settings['capacity']) * 1024; + settings['capacity'] = sizeInMB; + //These need to be deleted so they don't get passed to backend + delete settings['path']; + delete settings['newpool']; + //Create an empty storage volume and attach to VM if successful + createVol(settings, addVolSettings); + return true; + } else { + wok.message.error(i18n['KCHVMSTOR0002E'],'#alert-modal-container2'); + return false; + } + } else { + if (settings['pool'] && settings['vol']){ + // Delete path property since it's not needed for disk + delete settings['path']; + return true; + } + else { + wok.message.error(i18n['KCHVMSTOR0002E'],'#alert-modal-container2'); + return false; + } } }; @@ -172,6 +394,11 @@ kimchi.guest_storage_add_main = function() { if (submitButton.prop('disabled')) { return false; } + var bNewDisk = 'false'; + // Determine whether it's existing disk or new disk + if($(capacityTextbox).is(":visible") === true ) { + bNewDisk = 'true'; + } var formData = storageAddForm.serializeObject(); var settings = { @@ -179,40 +406,30 @@ kimchi.guest_storage_add_main = function() { type: typeTextbox.val(), path: pathTextbox.val(), pool: poolTextbox.val(), - vol: volTextbox.val() + vol: volTextbox.val(), + newpool: newPoolTextbox.val(), + format: formatTextbox.val(), + capacity: capacityTextbox.val() }; $(submitButton).prop('disabled', true); - $.each([pathTextbox, poolTextbox, volTextbox], function(i, c) { + $.each([pathTextbox, poolTextbox, volTextbox, newPoolTextbox, capacityTextbox, formatTextbox], function(i, c) { $(c).prop('disabled', true); }); // Validate form for cdrom and disk validateSpecifiedForm = validator[settings['type']]; if (!validateSpecifiedForm(settings)) { $(submitButton).prop('disabled', false); - $.each([submitButton, pathTextbox, poolTextbox, volTextbox], function(i, c) { + $.each([submitButton, pathTextbox, poolTextbox, volTextbox, newPoolTextbox, capacityTextbox, formatTextbox], function(i, c) { $(c).prop('disabled', false); }); return false; } $(submitButton).addClass('loading').text(i18n['KCHVMCD6003M']); - kimchi.addVMStorage(settings, function(result) { - wok.window.close(); - wok.topic('kimchi/vmCDROMAttached').publish({ - result: result - }); - }, function(result) { - var errText = result['reason'] || - result['responseJSON']['reason']; - wok.message.error(errText, '#alert-modal-container2'); - - $.each([submitButton, pathTextbox, poolTextbox, volTextbox], function(i, c) { - $(c).prop('disabled', false); - }); - $(submitButton).removeClass('loading').text(i18n['KCHVMCD6002M']); - }); - + if(bNewDisk === 'false'){ + addStorage(settings); + } event.preventDefault(); }; @@ -224,5 +441,8 @@ kimchi.guest_storage_add_main = function() { volTextbox.on('change propertychange', function (event) { $(submitButton).prop('disabled', $(this).val() === ''); }); + capacityTextbox.on('change input propertychange', function(event) { + $(submitButton).prop('disabled', $(this).val() === ''); + }); }; diff --git a/ui/pages/guest-storage-add.html.tmpl b/ui/pages/guest-storage-add.html.tmpl index bde0eee..660c274 100644 --- a/ui/pages/guest-storage-add.html.tmpl +++ b/ui/pages/guest-storage-add.html.tmpl @@ -1,7 +1,7 @@ #* * Project Kimchi * - * Copyright IBM, Corp. 2014 + * Copyright IBM, Corp. 2014-2016 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,18 +41,50 @@ </select> <p class="help-block"><i class="fa fa-info-circle"></i> $_("The device type. Currently, \"cdrom\" and \"disk\" are supported.")</p> </div> - <div class="volume-section hidden"> - <div class="form-group"> - <label>$_("Storage Pool")</label> - <select id="guest-disk-pool" class="selectpicker col-md-12 col-lg-12"> - </select> - <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage pool which volume located in")</p> + <div class="volume-section hidden form-group"> + <div class="template-modal-container"> + <div> + <span id="alert-modal-container"></span> + <input type="radio" checked="checked" name="disk-btn" id="new-disk" value="new-disk" class="wok-radio"> + <label for="new-disk">$_("Create a new disk")</label> + <input type="radio" name="disk-btn" id="existing-disk" value="existing-disk" class="wok-radio"> + <label for="existing-disk">$_("Select an existing disk")</label> + </div> </div> - <div class="form-group"> - <label>$_("Storage Volume")</label> - <select id="guest-disk-vol" class="selectpicker col-md-12 col-lg-12"> - </select> - <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage volume to be attached")</p> + <div class="template-pager"> + <div class="page" id="new-disk-box"> + <div class="form-group"> + <label>$_("Storage Pool")</label> + <select id="guest-disk-pool-new" class="selectpicker col-md-12 col-lg-12"> + </select> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage pool to create the volume in")</p> + </div> + <div class="form-group"> + <label>$_("Disk Size (GB)")</label> + <input type="number" class="form-control" name="capacity" min="1" id="capacity" /> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("New disk size to be created")</p> + </div> + <div class="form-group"> + <label>$_("Format")</label> + <select id="guest-disk-format-new" class="selectpicker col-md-12 col-lg-12"> + </select> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("Format of the new disk to be created")</p> + </div> + </div> + <div class="page" id="existing-disk-box"> + <div class="form-group"> + <label>$_("Storage Pool")</label> + <select id="guest-disk-pool" class="selectpicker col-md-12 col-lg-12"> + </select> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage pool in which the volume is located in")</p> + </div> + <div class="form-group"> + <label>$_("Storage Volume")</label> + <select id="guest-disk-vol" class="selectpicker col-md-12 col-lg-12"> + </select> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage volume to be attached")</p> + </div> + </div> </div> </div> <div class="path-section form-group"> @@ -72,4 +104,4 @@ kimchi.guest_storage_add_main(); </script> </body> -</html> \ No newline at end of file +</html> -- 1.9.1

Reviewed-by: Paulo Vital <pvital@linux.vnet.ibm.com> On 02/05/2016 02:14 PM, Socorro Stoppler wrote:
Signed-off-by: Socorro Stoppler <socorro@linux.vnet.ibm.com> --- ui/js/src/kimchi.guest_storage_add.main.js | 320 ++++++++++++++++++++++++----- ui/pages/guest-storage-add.html.tmpl | 58 ++++-- 2 files changed, 315 insertions(+), 63 deletions(-)
diff --git a/ui/js/src/kimchi.guest_storage_add.main.js b/ui/js/src/kimchi.guest_storage_add.main.js index 6e1926b..9c71486 100644 --- a/ui/js/src/kimchi.guest_storage_add.main.js +++ b/ui/js/src/kimchi.guest_storage_add.main.js @@ -15,6 +15,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +kimchi.switchPage = function(fromPageId, toPageId, direction) { + $('.tab-content').css('overflow', 'hidden'); + direction = direction || 'left'; + var toLeftBegin; + var fromLeftEnd; + if ('left' === direction) { + toLeftBegin = '100%'; + fromLeftEnd = '-100%'; + } else if ('right' === direction) { + toLeftBegin = '-100%'; + fromLeftEnd = '100%'; + } + var formPage = $('#' + fromPageId); + var toPage = $('#' + toPageId); + toPage.css({ + left: toLeftBegin + }); + formPage.animate({ + left: fromLeftEnd, + opacity: 0.1 + }, 400, function() { + $('.tab-content').css('overflow', 'visible'); + }); + toPage.animate({ + left: '0', + opacity: 1 + }, 400, function() { + $('.tab-content').css('overflow', 'visible'); + }); +}; + kimchi.guest_storage_add_main = function() { var types = [{ label: 'cdrom', @@ -35,9 +66,27 @@ kimchi.guest_storage_add_main = function() { var pathTextbox = $('input[name="path"]', storageAddForm); var poolTextbox = $('select#guest-disk-pool', storageAddForm); var volTextbox = $('select#guest-disk-vol', storageAddForm); + var newPoolTextbox = $('select#guest-disk-pool-new', storageAddForm); + var capacityTextbox = $('input[name="capacity"]', storageAddForm); + var formatTextbox = $('select#guest-disk-format-new', storageAddForm); var selectStorageTypeHTML = ''; var selectStoragePoolHTML = ''; var selectStorageVolHTML = ''; + var rbExisting = 'false'; + + var getFormatList = function() { + var format = ["bochs", "cloop", "cow", "dmg", "qcow", "qcow2", "qed", "raw", "vmdk", "vpc"]; + var selectFormatHTML = ''; + var i; + for (i = 0; i < format.length; i++) { + selectFormatHTML += '<option value="'+ format[i] + '">' + format[i] + '</option>'; + } + formatTextbox.empty(); + formatTextbox.append(selectFormatHTML); + $(formatTextbox).change(); + formatTextbox.selectpicker(); + $('.selectpicker').selectpicker('refresh'); + };
typeTextbox.change(function() { var pathObject = {'cdrom': ".path-section", 'disk': '.volume-section'}; @@ -45,41 +94,82 @@ kimchi.guest_storage_add_main = function() { $.each(pathObject, function(type, value) { if(selectType === type){ $(value).removeClass('hidden'); + } else if ((selectType === null) && (type === 'disk')) { + $(value).removeClass('hidden'); } else { $(value).addClass('hidden'); } }); - if ($(".path-section").hasClass('hidden')) { - $(poolTextbox).val('default'); - $(poolTextbox).change(); $(pathTextbox).val(""); - } - else { + if ($('#new-disk').checked) { + $('#existing-disk-box').addClass('hidden'); + $(newPoolTextbox).val('default'); + $(newPoolTextbox).change(); + } else if ($('#existing-disk').checked) { + $('#new-disk-box').addClass('hidden'); + $(poolTextbox).val('default'); + $(poolTextbox).change(); + } else { + if (rbExisting === 'true') { + $('#new-disk-box').addClass('hidden'); + } else { + $('#existing-disk-box').addClass('hidden'); + } + } + } else { $(poolTextbox).val(""); $(volTextbox).val(""); + $(newPoolTextbox).val(""); + $(capacityTextbox).val(""); + $(formatTextbox).val(""); } $('.selectpicker').selectpicker('refresh'); });
- kimchi.listStoragePools(function(result) { - var options = []; - if (result && result.length) { - $.each(result, function(index, storagePool) { - if ((storagePool.state==="active") && (storagePool.type !== 'kimchi-iso')) { - options.push({ - label: storagePool.name, - value: storagePool.name - }); - selectStoragePoolHTML += '<option value="'+ storagePool.name + '">' + storagePool.name + '</option>'; + var getStoragePools = function(radioButton) { + kimchi.listStoragePools(function(result) { + var options = []; + selectStoragePoolHTML = ''; //reset string + if (result && result.length) { + $.each(result, function(index, storagePool) { + if (radioButton === 'existing') { + if ((storagePool.state==="active") && (storagePool.type !== 'kimchi-iso')) { + options.push({ + label: storagePool.name, + value: storagePool.name + }); + selectStoragePoolHTML += '<option value="'+ storagePool.name + '">' + storagePool.name + '</option>'; + } + } else { //new disk + if ((storagePool.type != 'iscsi') && (storagePool.type != 'scsi')) { + options.push({ + label: storagePool.name, + value: storagePool.name + }); + selectStoragePoolHTML += '<option value="'+ storagePool.name + '">' + storagePool.name + '</option>'; + } } - }); - poolTextbox.append(selectStoragePoolHTML); - poolTextbox.val(options[0].value); - poolTextbox.selectpicker(); - } - }); + if (radioButton === 'existing') { + poolTextbox.empty(); + poolTextbox.append(selectStoragePoolHTML); + $(poolTextbox).change(); + poolTextbox.selectpicker(); + $('.selectpicker').selectpicker('refresh'); + } else if (radioButton === 'new') { //new disk + newPoolTextbox.empty(); + newPoolTextbox.append(selectStoragePoolHTML); + $(newPoolTextbox).val(options[0].value); + newPoolTextbox.selectpicker(); + getFormatList(); + } + } + }); + }; + + //First time retrieving list of Storage Pools - defaulting to new disk + getStoragePools('new');
poolTextbox.change(function() { var options = []; @@ -109,13 +199,15 @@ kimchi.guest_storage_add_main = function() { $(volTextbox).prop('disabled',true); $(submitButton).prop('disabled', true); } - volTextbox.selectpicker(); - $('.selectpicker').selectpicker('refresh'); + } else { + $(volTextbox).prop('disabled',true); + $(submitButton).prop('disabled', true); } + volTextbox.selectpicker(); + $('.selectpicker').selectpicker('refresh'); }, null, false); });
- typeTextbox.change(function() { var pathObject = {'cdrom': ".path-section", 'disk': '.volume-section'}; var selectType = $(this).val(); @@ -128,6 +220,47 @@ kimchi.guest_storage_add_main = function() { }); });
+ var currentPage = 'new-disk-box'; + $('#existing-disk').change(function() { + if (this.checked) { + rbExisting = 'true'; + if (currentPage === 'new-disk-box') { + kimchi.switchPage(currentPage, 'existing-disk-box', 'right'); + } + currentPage = 'existing-disk-box'; + $('#existing-disk-box').removeClass('hidden'); + $('#new-disk-box').addClass('hidden'); + $('#guest-storage-add-window .modal-body .template-pager').animate({ + height: "200px" + }, 300); + getStoragePools('existing'); + $(pathTextbox).val(""); + $(newPoolTextbox).val(""); + $(capacityTextbox).val(""); + $(formatTextbox).val(""); + } + }); + + $('#new-disk').change(function() { + if (this.checked) { + rbExisting = 'false'; + if (currentPage === 'existing-disk-box') { + kimchi.switchPage(currentPage, 'new-disk-box', 'right'); + } else if($(capacityTextbox).is(":visible") === false ) { + kimchi.switchPage(currentPage, 'new-disk-box', 'right'); + } + currentPage = 'new-disk-box'; + $('#existing-disk-box').addClass('hidden'); + $('#new-disk-box').removeClass('hidden'); + $('#guest-storage-add-window .modal-body .template-pager').animate({ + height: "300px" + }, 400); + $(pathTextbox).val(""); + $(poolTextbox).val(""); + $(volTextbox).val(""); + } + }); + if (kimchi.thisVMState === 'running') { types =typesRunning; $(typeTextbox).val('disk'); @@ -155,15 +288,104 @@ kimchi.guest_storage_add_main = function() { } };
+ var onError = function(result) { + if(!result) { + return; + } + var msg = result['message'] || ( + result['responseJSON'] && result['responseJSON']['reason'] + ); + wok.message.error(msg); + }; + + var addStorage = function(settings) { + kimchi.addVMStorage(settings, function(result) { + wok.window.close(); + wok.topic('kimchi/vmCDROMAttached').publish({ + result: result + }); + }, function(result) { + var errText = result['reason'] || + result['responseJSON']['reason']; + wok.message.error(errText, '#alert-modal-container2'); + $.each([submitButton, pathTextbox, poolTextbox, volTextbox, newPoolTextbox, capacityTextbox, formatTextbox], function(i, c) { + $(c).prop('disabled', false); + }); + }); + } + + var createVol = function(settings, addVolSettings) { + kimchi.createVolumeWithCapacity('default', { + name: settings['vol'], + format: settings['format'], + capacity: settings['capacity'] + }, function(result) { + var taskId = result.id; + function monitorTask() { + kimchi.getTask(taskId, function(result) { + var status = result.status; + if (status === "finished") { + //Now add newly created volume to VM + addStorage(addVolSettings); + } else if (status === "running") { + setTimeout(monitorTask, 2000); + $(submitButton).prop('disabled', true); + } else if (status === "failed") { + var errText = result['reason'] || + result['responseJSON']['reason']; + $(submitButton).prop('disabled', true); + wok.message.error(errText, '#alert-modal-container2'); + } + }); + } + setTimeout(monitorTask, 2000); + }, onError); + }; + + var bNewDisk = 'false'; + var validateDisk = function(settings) { - if (settings['pool'] && settings['vol']){ - // Delete path property since it's not needed for disk - delete settings['path']; - return true; + // Determine whether it's existing disk or new disk + if($(capacityTextbox).is(":visible") === true ) { + bNewDisk = 'true'; } - else { - wok.message.error(i18n['KCHVMSTOR0002E'],'#alert-modal-container2'); - return false; + if (bNewDisk === 'true') { + if (settings['newpool'] && settings['capacity'] && settings['format']){ + //Change settings['newpool'] to settings['pool'] + settings['pool']=settings['newpool']; + var vmname = settings['vm']; + vmname = vmname + new Date().getTime(); + //Unique vol name to be created + settings['vol']=vmname + ".img"; + //This is all that is needed for attaching newly created volume to VM + var addVolSettings = { + vm: settings['vm'], + type: settings['type'], + vol: settings['vol'], + pool: settings['pool'] + }; + var sizeInMB = parseInt(settings['capacity']) * 1024; + settings['capacity'] = sizeInMB; + //These need to be deleted so they don't get passed to backend + delete settings['path']; + delete settings['newpool']; + //Create an empty storage volume and attach to VM if successful + createVol(settings, addVolSettings); + return true; + } else { + wok.message.error(i18n['KCHVMSTOR0002E'],'#alert-modal-container2'); + return false; + } + } else { + if (settings['pool'] && settings['vol']){ + // Delete path property since it's not needed for disk + delete settings['path']; + return true; + } + else { + wok.message.error(i18n['KCHVMSTOR0002E'],'#alert-modal-container2'); + return false; + } } };
@@ -172,6 +394,11 @@ kimchi.guest_storage_add_main = function() { if (submitButton.prop('disabled')) { return false; } + var bNewDisk = 'false'; + // Determine whether it's existing disk or new disk + if($(capacityTextbox).is(":visible") === true ) { + bNewDisk = 'true'; + }
var formData = storageAddForm.serializeObject(); var settings = { @@ -179,40 +406,30 @@ kimchi.guest_storage_add_main = function() { type: typeTextbox.val(), path: pathTextbox.val(), pool: poolTextbox.val(), - vol: volTextbox.val() + vol: volTextbox.val(), + newpool: newPoolTextbox.val(), + format: formatTextbox.val(), + capacity: capacityTextbox.val() };
$(submitButton).prop('disabled', true); - $.each([pathTextbox, poolTextbox, volTextbox], function(i, c) { + $.each([pathTextbox, poolTextbox, volTextbox, newPoolTextbox, capacityTextbox, formatTextbox], function(i, c) { $(c).prop('disabled', true); }); // Validate form for cdrom and disk validateSpecifiedForm = validator[settings['type']]; if (!validateSpecifiedForm(settings)) { $(submitButton).prop('disabled', false); - $.each([submitButton, pathTextbox, poolTextbox, volTextbox], function(i, c) { + $.each([submitButton, pathTextbox, poolTextbox, volTextbox, newPoolTextbox, capacityTextbox, formatTextbox], function(i, c) { $(c).prop('disabled', false); }); return false; } $(submitButton).addClass('loading').text(i18n['KCHVMCD6003M']);
- kimchi.addVMStorage(settings, function(result) { - wok.window.close(); - wok.topic('kimchi/vmCDROMAttached').publish({ - result: result - }); - }, function(result) { - var errText = result['reason'] || - result['responseJSON']['reason']; - wok.message.error(errText, '#alert-modal-container2'); - - $.each([submitButton, pathTextbox, poolTextbox, volTextbox], function(i, c) { - $(c).prop('disabled', false); - }); - $(submitButton).removeClass('loading').text(i18n['KCHVMCD6002M']); - }); - + if(bNewDisk === 'false'){ + addStorage(settings); + } event.preventDefault(); };
@@ -224,5 +441,8 @@ kimchi.guest_storage_add_main = function() { volTextbox.on('change propertychange', function (event) { $(submitButton).prop('disabled', $(this).val() === ''); }); + capacityTextbox.on('change input propertychange', function(event) { + $(submitButton).prop('disabled', $(this).val() === ''); + });
}; diff --git a/ui/pages/guest-storage-add.html.tmpl b/ui/pages/guest-storage-add.html.tmpl index bde0eee..660c274 100644 --- a/ui/pages/guest-storage-add.html.tmpl +++ b/ui/pages/guest-storage-add.html.tmpl @@ -1,7 +1,7 @@ #* * Project Kimchi * - * Copyright IBM, Corp. 2014 + * Copyright IBM, Corp. 2014-2016 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,18 +41,50 @@ </select> <p class="help-block"><i class="fa fa-info-circle"></i> $_("The device type. Currently, \"cdrom\" and \"disk\" are supported.")</p> </div> - <div class="volume-section hidden"> - <div class="form-group"> - <label>$_("Storage Pool")</label> - <select id="guest-disk-pool" class="selectpicker col-md-12 col-lg-12"> - </select> - <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage pool which volume located in")</p> + <div class="volume-section hidden form-group"> + <div class="template-modal-container"> + <div> + <span id="alert-modal-container"></span> + <input type="radio" checked="checked" name="disk-btn" id="new-disk" value="new-disk" class="wok-radio"> + <label for="new-disk">$_("Create a new disk")</label> + <input type="radio" name="disk-btn" id="existing-disk" value="existing-disk" class="wok-radio"> + <label for="existing-disk">$_("Select an existing disk")</label> + </div> </div> - <div class="form-group"> - <label>$_("Storage Volume")</label> - <select id="guest-disk-vol" class="selectpicker col-md-12 col-lg-12"> - </select> - <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage volume to be attached")</p> + <div class="template-pager"> + <div class="page" id="new-disk-box"> + <div class="form-group"> + <label>$_("Storage Pool")</label> + <select id="guest-disk-pool-new" class="selectpicker col-md-12 col-lg-12"> + </select> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage pool to create the volume in")</p> + </div> + <div class="form-group"> + <label>$_("Disk Size (GB)")</label> + <input type="number" class="form-control" name="capacity" min="1" id="capacity" /> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("New disk size to be created")</p> + </div> + <div class="form-group"> + <label>$_("Format")</label> + <select id="guest-disk-format-new" class="selectpicker col-md-12 col-lg-12"> + </select> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("Format of the new disk to be created")</p> + </div> + </div> + <div class="page" id="existing-disk-box"> + <div class="form-group"> + <label>$_("Storage Pool")</label> + <select id="guest-disk-pool" class="selectpicker col-md-12 col-lg-12"> + </select> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage pool in which the volume is located in")</p> + </div> + <div class="form-group"> + <label>$_("Storage Volume")</label> + <select id="guest-disk-vol" class="selectpicker col-md-12 col-lg-12"> + </select> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage volume to be attached")</p> + </div> + </div> </div> </div> <div class="path-section form-group"> @@ -72,4 +104,4 @@ kimchi.guest_storage_add_main(); </script> </body> -</html> \ No newline at end of file +</html>

On 02/05/2016 02:14 PM, Socorro Stoppler wrote:
Signed-off-by: Socorro Stoppler <socorro@linux.vnet.ibm.com> --- ui/js/src/kimchi.guest_storage_add.main.js | 320 ++++++++++++++++++++++++----- ui/pages/guest-storage-add.html.tmpl | 58 ++++-- 2 files changed, 315 insertions(+), 63 deletions(-)
diff --git a/ui/js/src/kimchi.guest_storage_add.main.js b/ui/js/src/kimchi.guest_storage_add.main.js index 6e1926b..9c71486 100644 --- a/ui/js/src/kimchi.guest_storage_add.main.js +++ b/ui/js/src/kimchi.guest_storage_add.main.js @@ -15,6 +15,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +kimchi.switchPage = function(fromPageId, toPageId, direction) { + $('.tab-content').css('overflow', 'hidden'); + direction = direction || 'left'; + var toLeftBegin; + var fromLeftEnd; + if ('left' === direction) { + toLeftBegin = '100%'; + fromLeftEnd = '-100%'; + } else if ('right' === direction) { + toLeftBegin = '-100%'; + fromLeftEnd = '100%'; + } + var formPage = $('#' + fromPageId); + var toPage = $('#' + toPageId); + toPage.css({ + left: toLeftBegin + }); + formPage.animate({ + left: fromLeftEnd, + opacity: 0.1 + }, 400, function() { + $('.tab-content').css('overflow', 'visible'); + }); + toPage.animate({ + left: '0', + opacity: 1 + }, 400, function() { + $('.tab-content').css('overflow', 'visible'); + }); +}; + kimchi.guest_storage_add_main = function() { var types = [{ label: 'cdrom', @@ -35,9 +66,27 @@ kimchi.guest_storage_add_main = function() { var pathTextbox = $('input[name="path"]', storageAddForm); var poolTextbox = $('select#guest-disk-pool', storageAddForm); var volTextbox = $('select#guest-disk-vol', storageAddForm); + var newPoolTextbox = $('select#guest-disk-pool-new', storageAddForm); + var capacityTextbox = $('input[name="capacity"]', storageAddForm); + var formatTextbox = $('select#guest-disk-format-new', storageAddForm); var selectStorageTypeHTML = ''; var selectStoragePoolHTML = ''; var selectStorageVolHTML = ''; + var rbExisting = 'false'; + + var getFormatList = function() { + var format = ["bochs", "cloop", "cow", "dmg", "qcow", "qcow2", "qed", "raw", "vmdk", "vpc"];
Place 'qcow2' as the first option, so we implicitly guide user to use it.
+ var selectFormatHTML = ''; + var i; + for (i = 0; i < format.length; i++) { + selectFormatHTML += '<option value="'+ format[i] + '">' + format[i] + '</option>'; + } + formatTextbox.empty(); + formatTextbox.append(selectFormatHTML); + $(formatTextbox).change(); + formatTextbox.selectpicker(); + $('.selectpicker').selectpicker('refresh'); + };
typeTextbox.change(function() { var pathObject = {'cdrom': ".path-section", 'disk': '.volume-section'}; @@ -45,41 +94,82 @@ kimchi.guest_storage_add_main = function() { $.each(pathObject, function(type, value) { if(selectType === type){ $(value).removeClass('hidden'); + } else if ((selectType === null) && (type === 'disk')) { + $(value).removeClass('hidden'); } else { $(value).addClass('hidden'); } }); - if ($(".path-section").hasClass('hidden')) { - $(poolTextbox).val('default'); - $(poolTextbox).change(); $(pathTextbox).val(""); - } - else { + if ($('#new-disk').checked) { + $('#existing-disk-box').addClass('hidden'); + $(newPoolTextbox).val('default'); + $(newPoolTextbox).change(); + } else if ($('#existing-disk').checked) { + $('#new-disk-box').addClass('hidden'); + $(poolTextbox).val('default'); + $(poolTextbox).change(); + } else { + if (rbExisting === 'true') { + $('#new-disk-box').addClass('hidden'); + } else { + $('#existing-disk-box').addClass('hidden'); + } + } + } else { $(poolTextbox).val(""); $(volTextbox).val(""); + $(newPoolTextbox).val(""); + $(capacityTextbox).val(""); + $(formatTextbox).val(""); } $('.selectpicker').selectpicker('refresh'); });
- kimchi.listStoragePools(function(result) { - var options = []; - if (result && result.length) { - $.each(result, function(index, storagePool) { - if ((storagePool.state==="active") && (storagePool.type !== 'kimchi-iso')) { - options.push({ - label: storagePool.name, - value: storagePool.name - }); - selectStoragePoolHTML += '<option value="'+ storagePool.name + '">' + storagePool.name + '</option>'; + var getStoragePools = function(radioButton) { + kimchi.listStoragePools(function(result) { + var options = []; + selectStoragePoolHTML = ''; //reset string + if (result && result.length) { + $.each(result, function(index, storagePool) {
+ if (radioButton === 'existing') { + if ((storagePool.state==="active") && (storagePool.type !== 'kimchi-iso')) {
I don't know why it is not working, but the kimchi_isos pool is being listed to me in the combo box.
+ options.push({ + label: storagePool.name, + value: storagePool.name + }); + selectStoragePoolHTML += '<option value="'+ storagePool.name + '">' + storagePool.name + '</option>'; + } + } else { //new disk + if ((storagePool.type != 'iscsi') && (storagePool.type != 'scsi')) { + options.push({ + label: storagePool.name, + value: storagePool.name + }); + selectStoragePoolHTML += '<option value="'+ storagePool.name + '">' + storagePool.name + '</option>'; + } } - }); - poolTextbox.append(selectStoragePoolHTML); - poolTextbox.val(options[0].value); - poolTextbox.selectpicker(); - } - }); + if (radioButton === 'existing') { + poolTextbox.empty(); + poolTextbox.append(selectStoragePoolHTML); + $(poolTextbox).change(); + poolTextbox.selectpicker(); + $('.selectpicker').selectpicker('refresh'); + } else if (radioButton === 'new') { //new disk + newPoolTextbox.empty(); + newPoolTextbox.append(selectStoragePoolHTML); + $(newPoolTextbox).val(options[0].value); + newPoolTextbox.selectpicker(); + getFormatList(); + } + } + }); + }; + + //First time retrieving list of Storage Pools - defaulting to new disk + getStoragePools('new');
poolTextbox.change(function() { var options = []; @@ -109,13 +199,15 @@ kimchi.guest_storage_add_main = function() { $(volTextbox).prop('disabled',true); $(submitButton).prop('disabled', true); } - volTextbox.selectpicker(); - $('.selectpicker').selectpicker('refresh'); + } else { + $(volTextbox).prop('disabled',true); + $(submitButton).prop('disabled', true); } + volTextbox.selectpicker(); + $('.selectpicker').selectpicker('refresh'); }, null, false); });
- typeTextbox.change(function() { var pathObject = {'cdrom': ".path-section", 'disk': '.volume-section'}; var selectType = $(this).val(); @@ -128,6 +220,47 @@ kimchi.guest_storage_add_main = function() { }); });
+ var currentPage = 'new-disk-box'; + $('#existing-disk').change(function() { + if (this.checked) { + rbExisting = 'true'; + if (currentPage === 'new-disk-box') { + kimchi.switchPage(currentPage, 'existing-disk-box', 'right'); + } + currentPage = 'existing-disk-box'; + $('#existing-disk-box').removeClass('hidden'); + $('#new-disk-box').addClass('hidden'); + $('#guest-storage-add-window .modal-body .template-pager').animate({ + height: "200px" + }, 300); + getStoragePools('existing'); + $(pathTextbox).val(""); + $(newPoolTextbox).val(""); + $(capacityTextbox).val(""); + $(formatTextbox).val(""); + } + }); + + $('#new-disk').change(function() { + if (this.checked) { + rbExisting = 'false'; + if (currentPage === 'existing-disk-box') { + kimchi.switchPage(currentPage, 'new-disk-box', 'right'); + } else if($(capacityTextbox).is(":visible") === false ) { + kimchi.switchPage(currentPage, 'new-disk-box', 'right'); + } + currentPage = 'new-disk-box'; + $('#existing-disk-box').addClass('hidden'); + $('#new-disk-box').removeClass('hidden'); + $('#guest-storage-add-window .modal-body .template-pager').animate({ + height: "300px" + }, 400); + $(pathTextbox).val(""); + $(poolTextbox).val(""); + $(volTextbox).val(""); + } + }); + if (kimchi.thisVMState === 'running') { types =typesRunning; $(typeTextbox).val('disk'); @@ -155,15 +288,104 @@ kimchi.guest_storage_add_main = function() { } };
+ var onError = function(result) { + if(!result) { + return; + } + var msg = result['message'] || ( + result['responseJSON'] && result['responseJSON']['reason'] + ); + wok.message.error(msg); + }; + + var addStorage = function(settings) { + kimchi.addVMStorage(settings, function(result) { + wok.window.close(); + wok.topic('kimchi/vmCDROMAttached').publish({ + result: result + }); + }, function(result) { + var errText = result['reason'] || + result['responseJSON']['reason']; + wok.message.error(errText, '#alert-modal-container2'); + $.each([submitButton, pathTextbox, poolTextbox, volTextbox, newPoolTextbox, capacityTextbox, formatTextbox], function(i, c) { + $(c).prop('disabled', false); + }); + }); + } + + var createVol = function(settings, addVolSettings) { + kimchi.createVolumeWithCapacity('default', { + name: settings['vol'], + format: settings['format'], + capacity: settings['capacity'] + }, function(result) { + var taskId = result.id; + function monitorTask() { + kimchi.getTask(taskId, function(result) { + var status = result.status; + if (status === "finished") { + //Now add newly created volume to VM + addStorage(addVolSettings); + } else if (status === "running") { + setTimeout(monitorTask, 2000); + $(submitButton).prop('disabled', true); + } else if (status === "failed") { + var errText = result['reason'] || + result['responseJSON']['reason']; + $(submitButton).prop('disabled', true); + wok.message.error(errText, '#alert-modal-container2'); + } + }); + } + setTimeout(monitorTask, 2000); + }, onError); + }; + + var bNewDisk = 'false'; + var validateDisk = function(settings) { - if (settings['pool'] && settings['vol']){ - // Delete path property since it's not needed for disk - delete settings['path']; - return true; + // Determine whether it's existing disk or new disk + if($(capacityTextbox).is(":visible") === true ) { + bNewDisk = 'true'; } - else { - wok.message.error(i18n['KCHVMSTOR0002E'],'#alert-modal-container2'); - return false; + if (bNewDisk === 'true') { + if (settings['newpool'] && settings['capacity'] && settings['format']){ + //Change settings['newpool'] to settings['pool'] + settings['pool']=settings['newpool']; + var vmname = settings['vm']; + vmname = vmname + new Date().getTime(); + //Unique vol name to be created + settings['vol']=vmname + ".img"; + //This is all that is needed for attaching newly created volume to VM + var addVolSettings = { + vm: settings['vm'], + type: settings['type'], + vol: settings['vol'], + pool: settings['pool'] + }; + var sizeInMB = parseInt(settings['capacity']) * 1024; + settings['capacity'] = sizeInMB; + //These need to be deleted so they don't get passed to backend + delete settings['path']; + delete settings['newpool']; + //Create an empty storage volume and attach to VM if successful + createVol(settings, addVolSettings); + return true; + } else { + wok.message.error(i18n['KCHVMSTOR0002E'],'#alert-modal-container2'); + return false; + } + } else { + if (settings['pool'] && settings['vol']){ + // Delete path property since it's not needed for disk + delete settings['path']; + return true; + } + else { + wok.message.error(i18n['KCHVMSTOR0002E'],'#alert-modal-container2'); + return false; + } } };
@@ -172,6 +394,11 @@ kimchi.guest_storage_add_main = function() { if (submitButton.prop('disabled')) { return false; } + var bNewDisk = 'false'; + // Determine whether it's existing disk or new disk + if($(capacityTextbox).is(":visible") === true ) { + bNewDisk = 'true'; + }
var formData = storageAddForm.serializeObject(); var settings = { @@ -179,40 +406,30 @@ kimchi.guest_storage_add_main = function() { type: typeTextbox.val(), path: pathTextbox.val(), pool: poolTextbox.val(), - vol: volTextbox.val() + vol: volTextbox.val(), + newpool: newPoolTextbox.val(), + format: formatTextbox.val(), + capacity: capacityTextbox.val() };
$(submitButton).prop('disabled', true); - $.each([pathTextbox, poolTextbox, volTextbox], function(i, c) { + $.each([pathTextbox, poolTextbox, volTextbox, newPoolTextbox, capacityTextbox, formatTextbox], function(i, c) { $(c).prop('disabled', true); }); // Validate form for cdrom and disk validateSpecifiedForm = validator[settings['type']]; if (!validateSpecifiedForm(settings)) { $(submitButton).prop('disabled', false); - $.each([submitButton, pathTextbox, poolTextbox, volTextbox], function(i, c) { + $.each([submitButton, pathTextbox, poolTextbox, volTextbox, newPoolTextbox, capacityTextbox, formatTextbox], function(i, c) { $(c).prop('disabled', false); }); return false; } $(submitButton).addClass('loading').text(i18n['KCHVMCD6003M']);
- kimchi.addVMStorage(settings, function(result) { - wok.window.close(); - wok.topic('kimchi/vmCDROMAttached').publish({ - result: result - }); - }, function(result) { - var errText = result['reason'] || - result['responseJSON']['reason']; - wok.message.error(errText, '#alert-modal-container2'); - - $.each([submitButton, pathTextbox, poolTextbox, volTextbox], function(i, c) { - $(c).prop('disabled', false); - }); - $(submitButton).removeClass('loading').text(i18n['KCHVMCD6002M']); - }); - + if(bNewDisk === 'false'){ + addStorage(settings); + } event.preventDefault(); };
@@ -224,5 +441,8 @@ kimchi.guest_storage_add_main = function() { volTextbox.on('change propertychange', function (event) { $(submitButton).prop('disabled', $(this).val() === ''); }); + capacityTextbox.on('change input propertychange', function(event) { + $(submitButton).prop('disabled', $(this).val() === ''); + });
}; diff --git a/ui/pages/guest-storage-add.html.tmpl b/ui/pages/guest-storage-add.html.tmpl index bde0eee..660c274 100644 --- a/ui/pages/guest-storage-add.html.tmpl +++ b/ui/pages/guest-storage-add.html.tmpl @@ -1,7 +1,7 @@ #* * Project Kimchi * - * Copyright IBM, Corp. 2014 + * Copyright IBM, Corp. 2014-2016 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,18 +41,50 @@ </select> <p class="help-block"><i class="fa fa-info-circle"></i> $_("The device type. Currently, \"cdrom\" and \"disk\" are supported.")</p> </div> - <div class="volume-section hidden"> - <div class="form-group"> - <label>$_("Storage Pool")</label> - <select id="guest-disk-pool" class="selectpicker col-md-12 col-lg-12"> - </select> - <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage pool which volume located in")</p> + <div class="volume-section hidden form-group"> + <div class="template-modal-container"> + <div> + <span id="alert-modal-container"></span> + <input type="radio" checked="checked" name="disk-btn" id="new-disk" value="new-disk" class="wok-radio"> + <label for="new-disk">$_("Create a new disk")</label> + <input type="radio" name="disk-btn" id="existing-disk" value="existing-disk" class="wok-radio"> + <label for="existing-disk">$_("Select an existing disk")</label> + </div> </div> - <div class="form-group"> - <label>$_("Storage Volume")</label> - <select id="guest-disk-vol" class="selectpicker col-md-12 col-lg-12"> - </select> - <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage volume to be attached")</p> + <div class="template-pager"> + <div class="page" id="new-disk-box"> + <div class="form-group"> + <label>$_("Storage Pool")</label> + <select id="guest-disk-pool-new" class="selectpicker col-md-12 col-lg-12"> + </select> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage pool to create the volume in")</p> + </div> + <div class="form-group"> + <label>$_("Disk Size (GB)")</label> + <input type="number" class="form-control" name="capacity" min="1" id="capacity" /> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("New disk size to be created")</p> + </div> + <div class="form-group"> + <label>$_("Format")</label> + <select id="guest-disk-format-new" class="selectpicker col-md-12 col-lg-12"> + </select> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("Format of the new disk to be created")</p> + </div> + </div> + <div class="page" id="existing-disk-box"> + <div class="form-group"> + <label>$_("Storage Pool")</label> + <select id="guest-disk-pool" class="selectpicker col-md-12 col-lg-12"> + </select> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage pool in which the volume is located in")</p> + </div> + <div class="form-group"> + <label>$_("Storage Volume")</label> + <select id="guest-disk-vol" class="selectpicker col-md-12 col-lg-12"> + </select> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage volume to be attached")</p> + </div> + </div> </div> </div> <div class="path-section form-group"> @@ -72,4 +104,4 @@ kimchi.guest_storage_add_main(); </script> </body> -</html> \ No newline at end of file +</html>

Thanks for the comments. Will be sending v3 shortly. On 02/05/2016 11:25 AM, Aline Manera wrote:
On 02/05/2016 02:14 PM, Socorro Stoppler wrote:
Signed-off-by: Socorro Stoppler <socorro@linux.vnet.ibm.com> --- ui/js/src/kimchi.guest_storage_add.main.js | 320 ++++++++++++++++++++++++----- ui/pages/guest-storage-add.html.tmpl | 58 ++++-- 2 files changed, 315 insertions(+), 63 deletions(-)
diff --git a/ui/js/src/kimchi.guest_storage_add.main.js b/ui/js/src/kimchi.guest_storage_add.main.js index 6e1926b..9c71486 100644 --- a/ui/js/src/kimchi.guest_storage_add.main.js +++ b/ui/js/src/kimchi.guest_storage_add.main.js @@ -15,6 +15,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +kimchi.switchPage = function(fromPageId, toPageId, direction) { + $('.tab-content').css('overflow', 'hidden'); + direction = direction || 'left'; + var toLeftBegin; + var fromLeftEnd; + if ('left' === direction) { + toLeftBegin = '100%'; + fromLeftEnd = '-100%'; + } else if ('right' === direction) { + toLeftBegin = '-100%'; + fromLeftEnd = '100%'; + } + var formPage = $('#' + fromPageId); + var toPage = $('#' + toPageId); + toPage.css({ + left: toLeftBegin + }); + formPage.animate({ + left: fromLeftEnd, + opacity: 0.1 + }, 400, function() { + $('.tab-content').css('overflow', 'visible'); + }); + toPage.animate({ + left: '0', + opacity: 1 + }, 400, function() { + $('.tab-content').css('overflow', 'visible'); + }); +}; + kimchi.guest_storage_add_main = function() { var types = [{ label: 'cdrom', @@ -35,9 +66,27 @@ kimchi.guest_storage_add_main = function() { var pathTextbox = $('input[name="path"]', storageAddForm); var poolTextbox = $('select#guest-disk-pool', storageAddForm); var volTextbox = $('select#guest-disk-vol', storageAddForm); + var newPoolTextbox = $('select#guest-disk-pool-new', storageAddForm); + var capacityTextbox = $('input[name="capacity"]', storageAddForm); + var formatTextbox = $('select#guest-disk-format-new', storageAddForm); var selectStorageTypeHTML = ''; var selectStoragePoolHTML = ''; var selectStorageVolHTML = ''; + var rbExisting = 'false'; + + var getFormatList = function() { + var format = ["bochs", "cloop", "cow", "dmg", "qcow", "qcow2", "qed", "raw", "vmdk", "vpc"];
Place 'qcow2' as the first option, so we implicitly guide user to use it.
Fixed in v3.
+ var selectFormatHTML = ''; + var i; + for (i = 0; i < format.length; i++) { + selectFormatHTML += '<option value="'+ format[i] + '">' + format[i] + '</option>'; + } + formatTextbox.empty(); + formatTextbox.append(selectFormatHTML); + $(formatTextbox).change(); + formatTextbox.selectpicker(); + $('.selectpicker').selectpicker('refresh'); + };
typeTextbox.change(function() { var pathObject = {'cdrom': ".path-section", 'disk': '.volume-section'}; @@ -45,41 +94,82 @@ kimchi.guest_storage_add_main = function() { $.each(pathObject, function(type, value) { if(selectType === type){ $(value).removeClass('hidden'); + } else if ((selectType === null) && (type === 'disk')) { + $(value).removeClass('hidden'); } else { $(value).addClass('hidden'); } }); - if ($(".path-section").hasClass('hidden')) { - $(poolTextbox).val('default'); - $(poolTextbox).change(); $(pathTextbox).val(""); - } - else { + if ($('#new-disk').checked) { + $('#existing-disk-box').addClass('hidden'); + $(newPoolTextbox).val('default'); + $(newPoolTextbox).change(); + } else if ($('#existing-disk').checked) { + $('#new-disk-box').addClass('hidden'); + $(poolTextbox).val('default'); + $(poolTextbox).change(); + } else { + if (rbExisting === 'true') { + $('#new-disk-box').addClass('hidden'); + } else { + $('#existing-disk-box').addClass('hidden'); + } + } + } else { $(poolTextbox).val(""); $(volTextbox).val(""); + $(newPoolTextbox).val(""); + $(capacityTextbox).val(""); + $(formatTextbox).val(""); } $('.selectpicker').selectpicker('refresh'); });
- kimchi.listStoragePools(function(result) { - var options = []; - if (result && result.length) { - $.each(result, function(index, storagePool) { - if ((storagePool.state==="active") && (storagePool.type !== 'kimchi-iso')) { - options.push({ - label: storagePool.name, - value: storagePool.name - }); - selectStoragePoolHTML += '<option value="'+ storagePool.name + '">' + storagePool.name + '</option>'; + var getStoragePools = function(radioButton) { + kimchi.listStoragePools(function(result) { + var options = []; + selectStoragePoolHTML = ''; //reset string + if (result && result.length) { + $.each(result, function(index, storagePool) {
+ if (radioButton === 'existing') { + if ((storagePool.state==="active") && (storagePool.type !== 'kimchi-iso')) {
I don't know why it is not working, but the kimchi_isos pool is being listed to me in the combo box.
Fixed in v3. For this portion of the code, this is when existing disk. Added this change below for when it's new disk and 'kimchi_isos' no longer shows up.
+ options.push({ + label: storagePool.name, + value: storagePool.name + }); + selectStoragePoolHTML += '<option value="'+ storagePool.name + '">' + storagePool.name + '</option>'; + } + } else { //new disk + if ((storagePool.type != 'iscsi') && (storagePool.type != 'scsi')) { + options.push({ + label: storagePool.name, + value: storagePool.name + }); + selectStoragePoolHTML += '<option value="'+ storagePool.name + '">' + storagePool.name + '</option>'; + } } - }); - poolTextbox.append(selectStoragePoolHTML); - poolTextbox.val(options[0].value); - poolTextbox.selectpicker(); - } - }); + if (radioButton === 'existing') { + poolTextbox.empty(); + poolTextbox.append(selectStoragePoolHTML); + $(poolTextbox).change(); + poolTextbox.selectpicker(); + $('.selectpicker').selectpicker('refresh'); + } else if (radioButton === 'new') { //new disk + newPoolTextbox.empty(); + newPoolTextbox.append(selectStoragePoolHTML); + $(newPoolTextbox).val(options[0].value); + newPoolTextbox.selectpicker(); + getFormatList(); + } + } + }); + }; + + //First time retrieving list of Storage Pools - defaulting to new disk + getStoragePools('new');
poolTextbox.change(function() { var options = []; @@ -109,13 +199,15 @@ kimchi.guest_storage_add_main = function() { $(volTextbox).prop('disabled',true); $(submitButton).prop('disabled', true); } - volTextbox.selectpicker(); - $('.selectpicker').selectpicker('refresh'); + } else { + $(volTextbox).prop('disabled',true); + $(submitButton).prop('disabled', true); } + volTextbox.selectpicker(); + $('.selectpicker').selectpicker('refresh'); }, null, false); });
- typeTextbox.change(function() { var pathObject = {'cdrom': ".path-section", 'disk': '.volume-section'}; var selectType = $(this).val(); @@ -128,6 +220,47 @@ kimchi.guest_storage_add_main = function() { }); });
+ var currentPage = 'new-disk-box'; + $('#existing-disk').change(function() { + if (this.checked) { + rbExisting = 'true'; + if (currentPage === 'new-disk-box') { + kimchi.switchPage(currentPage, 'existing-disk-box', 'right'); + } + currentPage = 'existing-disk-box'; + $('#existing-disk-box').removeClass('hidden'); + $('#new-disk-box').addClass('hidden'); + $('#guest-storage-add-window .modal-body .template-pager').animate({ + height: "200px" + }, 300); + getStoragePools('existing'); + $(pathTextbox).val(""); + $(newPoolTextbox).val(""); + $(capacityTextbox).val(""); + $(formatTextbox).val(""); + } + }); + + $('#new-disk').change(function() { + if (this.checked) { + rbExisting = 'false'; + if (currentPage === 'existing-disk-box') { + kimchi.switchPage(currentPage, 'new-disk-box', 'right'); + } else if($(capacityTextbox).is(":visible") === false ) { + kimchi.switchPage(currentPage, 'new-disk-box', 'right'); + } + currentPage = 'new-disk-box'; + $('#existing-disk-box').addClass('hidden'); + $('#new-disk-box').removeClass('hidden'); + $('#guest-storage-add-window .modal-body .template-pager').animate({ + height: "300px" + }, 400); + $(pathTextbox).val(""); + $(poolTextbox).val(""); + $(volTextbox).val(""); + } + }); + if (kimchi.thisVMState === 'running') { types =typesRunning; $(typeTextbox).val('disk'); @@ -155,15 +288,104 @@ kimchi.guest_storage_add_main = function() { } };
+ var onError = function(result) { + if(!result) { + return; + } + var msg = result['message'] || ( + result['responseJSON'] && result['responseJSON']['reason'] + ); + wok.message.error(msg); + }; + + var addStorage = function(settings) { + kimchi.addVMStorage(settings, function(result) { + wok.window.close(); + wok.topic('kimchi/vmCDROMAttached').publish({ + result: result + }); + }, function(result) { + var errText = result['reason'] || + result['responseJSON']['reason']; + wok.message.error(errText, '#alert-modal-container2'); + $.each([submitButton, pathTextbox, poolTextbox, volTextbox, newPoolTextbox, capacityTextbox, formatTextbox], function(i, c) { + $(c).prop('disabled', false); + }); + }); + } + + var createVol = function(settings, addVolSettings) { + kimchi.createVolumeWithCapacity('default', { + name: settings['vol'], + format: settings['format'], + capacity: settings['capacity'] + }, function(result) { + var taskId = result.id; + function monitorTask() { + kimchi.getTask(taskId, function(result) { + var status = result.status; + if (status === "finished") { + //Now add newly created volume to VM + addStorage(addVolSettings); + } else if (status === "running") { + setTimeout(monitorTask, 2000); + $(submitButton).prop('disabled', true); + } else if (status === "failed") { + var errText = result['reason'] || + result['responseJSON']['reason']; + $(submitButton).prop('disabled', true); + wok.message.error(errText, '#alert-modal-container2'); + } + }); + } + setTimeout(monitorTask, 2000); + }, onError); + }; + + var bNewDisk = 'false'; + var validateDisk = function(settings) { - if (settings['pool'] && settings['vol']){ - // Delete path property since it's not needed for disk - delete settings['path']; - return true; + // Determine whether it's existing disk or new disk + if($(capacityTextbox).is(":visible") === true ) { + bNewDisk = 'true'; } - else { - wok.message.error(i18n['KCHVMSTOR0002E'],'#alert-modal-container2'); - return false; + if (bNewDisk === 'true') { + if (settings['newpool'] && settings['capacity'] && settings['format']){ + //Change settings['newpool'] to settings['pool'] + settings['pool']=settings['newpool']; + var vmname = settings['vm']; + vmname = vmname + new Date().getTime(); + //Unique vol name to be created + settings['vol']=vmname + ".img"; + //This is all that is needed for attaching newly created volume to VM + var addVolSettings = { + vm: settings['vm'], + type: settings['type'], + vol: settings['vol'], + pool: settings['pool'] + }; + var sizeInMB = parseInt(settings['capacity']) * 1024; + settings['capacity'] = sizeInMB; + //These need to be deleted so they don't get passed to backend + delete settings['path']; + delete settings['newpool']; + //Create an empty storage volume and attach to VM if successful + createVol(settings, addVolSettings); + return true; + } else { + wok.message.error(i18n['KCHVMSTOR0002E'],'#alert-modal-container2'); + return false; + } + } else { + if (settings['pool'] && settings['vol']){ + // Delete path property since it's not needed for disk + delete settings['path']; + return true; + } + else { + wok.message.error(i18n['KCHVMSTOR0002E'],'#alert-modal-container2'); + return false; + } } };
@@ -172,6 +394,11 @@ kimchi.guest_storage_add_main = function() { if (submitButton.prop('disabled')) { return false; } + var bNewDisk = 'false'; + // Determine whether it's existing disk or new disk + if($(capacityTextbox).is(":visible") === true ) { + bNewDisk = 'true'; + }
var formData = storageAddForm.serializeObject(); var settings = { @@ -179,40 +406,30 @@ kimchi.guest_storage_add_main = function() { type: typeTextbox.val(), path: pathTextbox.val(), pool: poolTextbox.val(), - vol: volTextbox.val() + vol: volTextbox.val(), + newpool: newPoolTextbox.val(), + format: formatTextbox.val(), + capacity: capacityTextbox.val() };
$(submitButton).prop('disabled', true); - $.each([pathTextbox, poolTextbox, volTextbox], function(i, c) { + $.each([pathTextbox, poolTextbox, volTextbox, newPoolTextbox, capacityTextbox, formatTextbox], function(i, c) { $(c).prop('disabled', true); }); // Validate form for cdrom and disk validateSpecifiedForm = validator[settings['type']]; if (!validateSpecifiedForm(settings)) { $(submitButton).prop('disabled', false); - $.each([submitButton, pathTextbox, poolTextbox, volTextbox], function(i, c) { + $.each([submitButton, pathTextbox, poolTextbox, volTextbox, newPoolTextbox, capacityTextbox, formatTextbox], function(i, c) { $(c).prop('disabled', false); }); return false; } $(submitButton).addClass('loading').text(i18n['KCHVMCD6003M']);
- kimchi.addVMStorage(settings, function(result) { - wok.window.close(); - wok.topic('kimchi/vmCDROMAttached').publish({ - result: result - }); - }, function(result) { - var errText = result['reason'] || - result['responseJSON']['reason']; - wok.message.error(errText, '#alert-modal-container2'); - - $.each([submitButton, pathTextbox, poolTextbox, volTextbox], function(i, c) { - $(c).prop('disabled', false); - }); - $(submitButton).removeClass('loading').text(i18n['KCHVMCD6002M']); - }); - + if(bNewDisk === 'false'){ + addStorage(settings); + } event.preventDefault(); };
@@ -224,5 +441,8 @@ kimchi.guest_storage_add_main = function() { volTextbox.on('change propertychange', function (event) { $(submitButton).prop('disabled', $(this).val() === ''); }); + capacityTextbox.on('change input propertychange', function(event) { + $(submitButton).prop('disabled', $(this).val() === ''); + });
}; diff --git a/ui/pages/guest-storage-add.html.tmpl b/ui/pages/guest-storage-add.html.tmpl index bde0eee..660c274 100644 --- a/ui/pages/guest-storage-add.html.tmpl +++ b/ui/pages/guest-storage-add.html.tmpl @@ -1,7 +1,7 @@ #* * Project Kimchi * - * Copyright IBM, Corp. 2014 + * Copyright IBM, Corp. 2014-2016 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,18 +41,50 @@ </select> <p class="help-block"><i class="fa fa-info-circle"></i> $_("The device type. Currently, \"cdrom\" and \"disk\" are supported.")</p> </div> - <div class="volume-section hidden"> - <div class="form-group"> - <label>$_("Storage Pool")</label> - <select id="guest-disk-pool" class="selectpicker col-md-12 col-lg-12"> - </select> - <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage pool which volume located in")</p> + <div class="volume-section hidden form-group"> + <div class="template-modal-container"> + <div> + <span id="alert-modal-container"></span> + <input type="radio" checked="checked" name="disk-btn" id="new-disk" value="new-disk" class="wok-radio"> + <label for="new-disk">$_("Create a new disk")</label> + <input type="radio" name="disk-btn" id="existing-disk" value="existing-disk" class="wok-radio"> + <label for="existing-disk">$_("Select an existing disk")</label> + </div> </div> - <div class="form-group"> - <label>$_("Storage Volume")</label> - <select id="guest-disk-vol" class="selectpicker col-md-12 col-lg-12"> - </select> - <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage volume to be attached")</p> + <div class="template-pager"> + <div class="page" id="new-disk-box"> + <div class="form-group"> + <label>$_("Storage Pool")</label> + <select id="guest-disk-pool-new" class="selectpicker col-md-12 col-lg-12"> + </select> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage pool to create the volume in")</p> + </div> + <div class="form-group"> + <label>$_("Disk Size (GB)")</label> + <input type="number" class="form-control" name="capacity" min="1" id="capacity" /> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("New disk size to be created")</p> + </div> + <div class="form-group"> + <label>$_("Format")</label> + <select id="guest-disk-format-new" class="selectpicker col-md-12 col-lg-12"> + </select> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("Format of the new disk to be created")</p> + </div> + </div> + <div class="page" id="existing-disk-box"> + <div class="form-group"> + <label>$_("Storage Pool")</label> + <select id="guest-disk-pool" class="selectpicker col-md-12 col-lg-12"> + </select> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage pool in which the volume is located in")</p> + </div> + <div class="form-group"> + <label>$_("Storage Volume")</label> + <select id="guest-disk-vol" class="selectpicker col-md-12 col-lg-12"> + </select> + <p class="help-block"><i class="fa fa-info-circle"></i> $_("Storage volume to be attached")</p> + </div> + </div> </div> </div> <div class="path-section form-group"> @@ -72,4 +104,4 @@ kimchi.guest_storage_add_main(); </script> </body> -</html> \ No newline at end of file +</html>

Hi Socorro, Just 2 comments: - When listing the storage pools, please, do not display 'kimchi_isos' as it is exclusive for Kimchi proposals and user should not be aware about it - Set the default format as qcow2. It is displaying 'bochs' because of the alphabetic order, I think. Regards, Aline Manera On 02/05/2016 02:14 PM, Socorro Stoppler wrote:
V2: Add ".img" to name of new volume being created
V1: This is the initial checkin for creating a new volume and attaching to VM when editing a guest. Not sure of all the supported cases, but tried default pool and qcow2 format per the example provided in the back-end patch and it was successful.
Known issue - which currently exists in the code already in master - code is broken when VM is running. This patch does not address that. It will get addressed separately.
Socorro Stoppler (1): Create new volume and attach to VM Add ".img" to name of new volume
ui/js/src/kimchi.guest_storage_add.main.js | 320 ++++++++++++++++++++++++----- ui/pages/guest-storage-add.html.tmpl | 58 ++++-- 2 files changed, 315 insertions(+), 63 deletions(-)
participants (3)
-
Aline Manera
-
Paulo Vital
-
Socorro Stoppler