
Volume. Known kssues: after add a remote URL and click "OK" button, manual click on the storage pool to refresh the volumes. Signed-off-by: Hongliang Wang <hlwang@linux.vnet.ibm.com> --- src/kimchi/model/host.py | 2 +- ui/css/theme-default/sp-add-volume.css | 36 ++++++++ ui/css/theme-default/storage.css | 34 +++++++- ui/js/src/kimchi.api.js | 38 +++++++++ ui/js/src/kimchi.sp_add_volume_main.js | 152 +++++++++++++++++++++++++++++++++ ui/js/src/kimchi.storage_main.js | 19 ++++- ui/pages/i18n.json.tmpl | 3 + ui/pages/sp-add-volume.html.tmpl | 80 +++++++++++++++++ ui/pages/tabs/storage.html.tmpl | 12 ++- 9 files changed, 369 insertions(+), 7 deletions(-) create mode 100644 ui/css/theme-default/sp-add-volume.css create mode 100644 ui/js/src/kimchi.sp_add_volume_main.js create mode 100644 ui/pages/sp-add-volume.html.tmpl diff --git a/src/kimchi/model/host.py b/src/kimchi/model/host.py index 933a142..2052f58 100644 --- a/src/kimchi/model/host.py +++ b/src/kimchi/model/host.py @@ -280,7 +280,7 @@ class DevicesModel(object): def __init__(self, **kargs): self.conn = kargs['conn'] self.cap_map = \ - {'fc_host': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_FC_HOST, + {#'fc_host': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_FC_HOST, 'net': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_NET, 'pci': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_PCI_DEV, 'scsi': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_SCSI, diff --git a/ui/css/theme-default/sp-add-volume.css b/ui/css/theme-default/sp-add-volume.css new file mode 100644 index 0000000..2c31b9a --- /dev/null +++ b/ui/css/theme-default/sp-add-volume.css @@ -0,0 +1,36 @@ +/* + * Project Kimchi + * + * Copyright IBM, Corp. 2013-2014 + * + * Licensed 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. + */ +#sp-add-volume-window { + height: 400px; + width: 500px; +} + +#sp-add-volume-window .textbox-wrapper input[type="text"] { + box-sizing: border-box; + width: 100%; +} + +#sp-add-volume-window .textbox-wrapper label { + vertical-align: middle; +} + +#sp-add-volume-window input[type="text"][disabled] { + color: #bbb; + background-color: #fafafa; + cursor: not-allowed; +} diff --git a/ui/css/theme-default/storage.css b/ui/css/theme-default/storage.css index f635c2f..02c92e9 100644 --- a/ui/css/theme-default/storage.css +++ b/ui/css/theme-default/storage.css @@ -336,7 +336,6 @@ float: left; padding: 4px; margin-bottom: 5px; - height: 40px; width: 130px; } @@ -622,3 +621,36 @@ #iSCSITarget input { width: 493px; } + +/* Progress bar */ +.volume-progress { + clear: both; + width: 140px; +} + +.volume-progress .progress-bar-outer { + background: #ccc; + height: 4px; + overflow: hidden; + width: 100%; +} + +.volume-progress .progress-bar-inner { + background: #090; + height: 100%; + width: 0%; +} + +.volume-progress .progress-label { + color: #999; + font-size: 10px; + line-height: 16px; +} + +.volume-progress .progress-status { +} + +.volume-progress .progress-downloaded { + float: right; +} +/* End of Progress bar */ diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js index 5fc456d..766df6a 100644 --- a/ui/js/src/kimchi.api.js +++ b/ui/js/src/kimchi.api.js @@ -1130,5 +1130,43 @@ var kimchi = { kimchi.message.error(data.responseJSON.reason); } }); + }, + + /** + * Add a volume to a given storage pool. + */ + uploadVolumeToSP: function(settings, suc, err) { + var fd = settings['formData']; + var sp = encodeURIComponent(settings['sp']); + kimchi.requestJSON({ + url : kimchi.url + 'storagepools/' + sp + '/storagevolumes', + type : 'POST', + data : fd, + processData : false, + contentType : false, + success : suc, + error : err + }); + }, + + /** + * Add a volume to a given storage pool by URL. + */ + downloadVolumeToSP: function(settings, suc, err) { + var url = settings['url']; + var name = settings['name']; + var sp = encodeURIComponent(settings['sp']); + kimchi.requestJSON({ + url : kimchi.url + 'storagepools/' + sp + '/storagevolumes', + type : 'POST', + data : JSON.stringify({ + name: name, + url: url + }), + contentType : 'application/json', + dataType : 'json', + success : suc, + error : err + }); } }; diff --git a/ui/js/src/kimchi.sp_add_volume_main.js b/ui/js/src/kimchi.sp_add_volume_main.js new file mode 100644 index 0000000..e4f25fc --- /dev/null +++ b/ui/js/src/kimchi.sp_add_volume_main.js @@ -0,0 +1,152 @@ +/* + * Project Kimchi + * + * Copyright IBM, Corp. 2013-2014 + * + * Licensed 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. + */ +kimchi.sp_add_volume_main = function() { + // download from remote server or upload from local file + var type = 'download'; + + var addButton = $('#sp-add-volume-button'); + + $('input.volume-type').change(function(e) { + $('.volume-input').prop('disabled', true); + $('.volume-input.' + this.value).prop('disabled', false); + type = this.value; + }); + + var volumeName; + var taskID = -1; + + var onAccepted = function() { + if(inited[taskID]) { + return; + } + inited[taskID] = true; + kimchi.window.close(); + + kimchi.topic('kimchi/volumeDownloadStarted').publish({ + sp: kimchi.selectedSP + }); + }; + var inited = {}; + + var onError = function(result) { + if (result['message']) { + var errText = result['message']; + } + else { + var errText = result['responseJSON']['reason']; + } + result && kimchi.message.error(errText); + var volumeName = result['target_uri'].split('/').pop(); + var volumeBox = $('#volume' + kimchi.selectedSP + ' [data-volume-name="' + volumeName + '"]'); + $('.progress-status', volumeBox).text(i18n['KCHPOOL6016M']); + }; + + var fetchRemoteFile = function() { + var volumeURL = $('#volume-remote-url').val(); + volumeName = volumeURL.split(/(\\|\/)/g).pop(); + kimchi.downloadVolumeToSP({ + sp: kimchi.selectedSP, + name: volumeName, + url: volumeURL + }, function(resp) { + taskID = resp['id']; + onAccepted(); + }, onError); + }; + + var onProgress = function(resp) { + var sizeArray = resp['message'].split('/'); + var downloaded = sizeArray[0]; + var percent = 0; + if(isNaN(downloaded)) { + downloaded = 0; + } + else { + var total = sizeArray[1]; + percent = downloaded / total * 100; + } + var formatted = kimchi.formatMeasurement(downloaded); + var size = formatted['v'].toFixed(1) + formatted['s']; + var volumeBox = $('#volume' + kimchi.selectedSP + ' [data-volume-name="' + volumeName + '"]'); + $('.volume-progress', volumeBox).removeClass('hidden'); + $('.progress-bar-inner', volumeBox).css({ + width: percent + '%' + }); + $('.progress-status', volumeBox).text(i18n['KCHPOOL6014M']); + $('.progress-downloaded', volumeBox).text(size); + }; + + var onTaskResponse = function(result) { + var taskStatus = result['status']; + switch(taskStatus) { + case 'running': + onProgress(result); + setTimeout(function() { + trackTask(result['id']); + }, 1500); + break; + case 'finished': + taskID = -1; + var volumeName = result['target_url'].split('/').pop(); + var volumeBox = $('#volume' + kimchi.selectedSP + ' [data-volume-name="' + volumeName + '"]'); + $('.progress-status', volumeBox).text(i18n['KCHPOOL6015M']); + break; + case 'failed': + taskID = -1; + onError(result); + break; + default: + break; + } + }; + + var trackTask = function(task) { + kimchi.getTask(task, onTaskResponse, function(resp) {}); + }; + + var uploadFile = function() { + var blobFile = $('#volume-input-file')[0].files[0]; + var fileName = blobFile.name; + var fd = new FormData(); + fd.append('name', fileName); + fd.append('file', blobFile); + kimchi.uploadVolumeToSP({ + sp: kimchi.selectedSP, + formData: fd + }, function(resp) { + }); + }; + + $(addButton).on('click', function(event) { + $(this).prop('disabled', true); + if(type === 'download') { + fetchRemoteFile(); + } + else { + uploadFile(); + } + event.preventDefault(); + }); + + kimchi.topic('kimchi/volumesListed').subscribe(function() { + var volumeBox = $('#volume' + kimchi.selectedSP + ' [data-volume-name="' + volumeName + '"]'); + $(volumeBox).removeClass('hidden'); + $('.progress-status', volumeBox).text(i18n['KCHPOOL6014M']); + taskID > -1 && trackTask(taskID); + }); +}; diff --git a/ui/js/src/kimchi.storage_main.js b/ui/js/src/kimchi.storage_main.js index ae3f963..56252a8 100644 --- a/ui/js/src/kimchi.storage_main.js +++ b/ui/js/src/kimchi.storage_main.js @@ -135,6 +135,12 @@ kimchi.storageBindClick = function() { } }); + $('.pool-add-volume').on('click', function(event) { + var poolName = $(this).data('name'); + kimchi.selectedSP = poolName; + kimchi.window.open('sp-add-volume.html'); + }); + $('.storage-action').on('click', function() { var storage_action = $(this); var deleteButton = storage_action.find('.pool-delete'); @@ -149,10 +155,6 @@ kimchi.storageBindClick = function() { $("#logicalPoolExtend").dialog("option", "poolName", $(this).data('name')); $("#logicalPoolExtend").dialog("open"); }); - - $('#volume-doAdd').on('click', function() { - kimchi.window.open('storagevolume-add.html'); - }); } $('.storage-li').on('click', function(event) { @@ -195,6 +197,9 @@ kimchi.doListVolumes = function(poolObj) { poolObj.removeClass('in'); kimchi.changeArrow(handleArrow); slide.slideDown('slow'); + setTimeout(function() { + kimchi.topic('kimchi/volumesListed').publish(); + }, 1500); } }, function(err) { kimchi.message.error(err.responseJSON.reason); @@ -255,6 +260,12 @@ kimchi.storage_main = function() { } kimchi.doListStoragePools(); kimchi.initLogicalPoolExtend(); + + kimchi.topic('kimchi/volumeDownloadStarted').subscribe(function(data) { + var sp = data['sp']; + var poolNode = $('.storage-li[data-name="' + sp + '"]'); + kimchi.doListVolumes(poolNode); + }); } kimchi.changeArrow = function(obj) { diff --git a/ui/pages/i18n.json.tmpl b/ui/pages/i18n.json.tmpl index d920ae2..b3bca53 100644 --- a/ui/pages/i18n.json.tmpl +++ b/ui/pages/i18n.json.tmpl @@ -169,6 +169,9 @@ "KCHPOOL6011M": "$_("No available partitions found.")", "KCHPOOL6012M": "$_("This storage pool is not persistent. Instead of deactivate, this action will permanently delete it. Would you like to continue?")", "KCHPOOL6013M": "$_("Unable to retrieve partitions information.")", + "KCHPOOL6014M": "$_("Downloading...")", + "KCHPOOL6015M": "$_("Done!")", + "KCHPOOL6016M": "$_("Failed!")", "KCHVMSTOR0001E": "$_("CDROM path need to be a valid local path and cannot be blank.")", "KCHVMSTOR0002E": "$_("Disk pool or volume cannot be blank.")" diff --git a/ui/pages/sp-add-volume.html.tmpl b/ui/pages/sp-add-volume.html.tmpl new file mode 100644 index 0000000..148a17b --- /dev/null +++ b/ui/pages/sp-add-volume.html.tmpl @@ -0,0 +1,80 @@ +#* + * Project Kimchi + * + * Copyright IBM, Corp. 2014 + * + * Authors: + * Hongliang Wang <hlwang@linux.vnet.ibm.com> + * + * Licensed 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. + *# +#unicode UTF-8 +#import gettext +#from kimchi.cachebust import href +#silent t = gettext.translation($lang.domain, $lang.localedir, languages=$lang.lang) +#silent _ = t.gettext +#silent _t = t.gettext +<div id="sp-add-volume-window" class="window"> + <form id="form-sp-add-volume"> + <header class="window-header"> + <h1 class="title">$_("Add a Volume to Storage Pool")</h1> + <div class="close">X</div> + </header> + <section> + <div class="content"> + <div class="form-section"> + <h2> + <input type="radio" id="volume-type-download" class="volume-type" name="volumeType" value="download" checked="checked" /> + <label for="volume-type-download"> + $_("Fetch from remote URL") + </label> + </h2> + <div class="field"> + <p class="text-help"> + $_("Enter the remote URL here.") + </p> + <div class="textbox-wrapper"> + <input type="text" id="volume-remote-url" class="text volume-input download" name="volumeRemoteURL" /> + </div> + </div> + </div> + <div class="form-section"> + <h2> + <input type="radio" id="volume-type-upload" class="volume-type" name="volumeType" value="upload"/> + <label for="volume-type-upload"> + $_("Upload an file") + </label> + </h2> + <div class="field"> + <p class="text-help"> + $_("Choose the file you want to upload.") + </p> + <div class="textbox-wrapper"> + <input type="file" class="volume-input upload" id="volume-input-file" name="volumeLocalFile" disabled="disabled" /> + </div> + </div> + </div> + </div> + </section> + <footer> + <div class="btn-group"> + <button type="submit" id="sp-add-volume-button" class="btn-normal"> + <span class="text">$_("OK")</span> + </button> + </div> + </footer> + </form> +</div> +<script type="text/javascript"> + kimchi.sp_add_volume_main(); +</script> diff --git a/ui/pages/tabs/storage.html.tmpl b/ui/pages/tabs/storage.html.tmpl index 87205bd..d5aceef 100644 --- a/ui/pages/tabs/storage.html.tmpl +++ b/ui/pages/tabs/storage.html.tmpl @@ -82,6 +82,7 @@ <div class="popover actionsheet right-side" style="width: 250px"> <button class="button-big pool-deactivate" data-stat="{state}" data-name="{name}" data-persistent="{persistent}"><span class="text">$_("Deactivate")</span></button> <button class="button-big pool-activate" data-stat="{state}" data-name="{name}"><span class="text">$_("Activate")</span></button> + <button class="button-big pool-add-volume" data-stat="{state}" data-name="{name}"><span class="text">$_("Add Volume")</span></button> <button class="button-big pool-extend {enableExt}" data-stat="{state}" data-name="{name}"><span class="text">$_("Extend")</span></button> <button class="button-big red pool-delete" data-stat="{state}" data-name="{name}"><span class="text">$_("Undefine")</span></button> </div> @@ -98,11 +99,20 @@ </li> </script> <script id="volumeTmpl" type="html/text"> - <div class="volume-box white-box"> + <div class="volume-box white-box" data-volume-name="{name}"> <div class="storage-icon volume-default icon-{format} "> </div> <div class="volume-title"> <div class="volume-name" title="{name}">{name}</div> + <div class="volume-progress hidden"> + <div class="progress-bar-outer"> + <div class="progress-bar-inner"></div> + </div> + <div class="progress-label"> + <span class="progress-status"></span> + <span class="progress-downloaded"></span> + </div> + </div> </div> <div class="volume-setting"> </div> -- 1.8.1.4

On 09/05/2014 02:48 PM, Hongliang Wang wrote:
Volume.
Known kssues: after add a remote URL and click "OK" button, manual click on the storage pool to refresh the volumes.
Signed-off-by: Hongliang Wang <hlwang@linux.vnet.ibm.com> --- src/kimchi/model/host.py | 2 +- ui/css/theme-default/sp-add-volume.css | 36 ++++++++ ui/css/theme-default/storage.css | 34 +++++++- ui/js/src/kimchi.api.js | 38 +++++++++ ui/js/src/kimchi.sp_add_volume_main.js | 152 +++++++++++++++++++++++++++++++++ ui/js/src/kimchi.storage_main.js | 19 ++++- ui/pages/i18n.json.tmpl | 3 + ui/pages/sp-add-volume.html.tmpl | 80 +++++++++++++++++ ui/pages/tabs/storage.html.tmpl | 12 ++- 9 files changed, 369 insertions(+), 7 deletions(-) create mode 100644 ui/css/theme-default/sp-add-volume.css create mode 100644 ui/js/src/kimchi.sp_add_volume_main.js create mode 100644 ui/pages/sp-add-volume.html.tmpl
diff --git a/src/kimchi/model/host.py b/src/kimchi/model/host.py index 933a142..2052f58 100644 --- a/src/kimchi/model/host.py +++ b/src/kimchi/model/host.py @@ -280,7 +280,7 @@ class DevicesModel(object): def __init__(self, **kargs): self.conn = kargs['conn'] self.cap_map = \ - {'fc_host': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_FC_HOST, + {#'fc_host': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_FC_HOST,
I think you used it only for your tests, right? =)
'net': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_NET, 'pci': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_PCI_DEV, 'scsi': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_SCSI, diff --git a/ui/css/theme-default/sp-add-volume.css b/ui/css/theme-default/sp-add-volume.css new file mode 100644 index 0000000..2c31b9a --- /dev/null +++ b/ui/css/theme-default/sp-add-volume.css @@ -0,0 +1,36 @@ +/* + * Project Kimchi + * + * Copyright IBM, Corp. 2013-2014 + *
The copyright refers to the year the file was created followed by the modifications. So if a file was created in 2014 and never modified the copyright will be only 2014. if the files was created in 2013 and modified in 2014 the copyright will be 2013-2014 So in this case you need to keep only 2014
+ * Licensed 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. + */ +#sp-add-volume-window { + height: 400px; + width: 500px; +} + +#sp-add-volume-window .textbox-wrapper input[type="text"] { + box-sizing: border-box; + width: 100%; +} + +#sp-add-volume-window .textbox-wrapper label { + vertical-align: middle; +} + +#sp-add-volume-window input[type="text"][disabled] { + color: #bbb; + background-color: #fafafa; + cursor: not-allowed; +} diff --git a/ui/css/theme-default/storage.css b/ui/css/theme-default/storage.css index f635c2f..02c92e9 100644 --- a/ui/css/theme-default/storage.css +++ b/ui/css/theme-default/storage.css @@ -336,7 +336,6 @@ float: left; padding: 4px; margin-bottom: 5px; - height: 40px; width: 130px; }
@@ -622,3 +621,36 @@ #iSCSITarget input { width: 493px; } + +/* Progress bar */ +.volume-progress { + clear: both; + width: 140px; +} + +.volume-progress .progress-bar-outer { + background: #ccc; + height: 4px; + overflow: hidden; + width: 100%; +} + +.volume-progress .progress-bar-inner { + background: #090; + height: 100%; + width: 0%; +} + +.volume-progress .progress-label { + color: #999; + font-size: 10px; + line-height: 16px; +} + +.volume-progress .progress-status { +} + +.volume-progress .progress-downloaded { + float: right; +} +/* End of Progress bar */ diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js index 5fc456d..766df6a 100644 --- a/ui/js/src/kimchi.api.js +++ b/ui/js/src/kimchi.api.js @@ -1130,5 +1130,43 @@ var kimchi = { kimchi.message.error(data.responseJSON.reason); } }); + }, + + /** + * Add a volume to a given storage pool. + */ + uploadVolumeToSP: function(settings, suc, err) { + var fd = settings['formData']; + var sp = encodeURIComponent(settings['sp']); + kimchi.requestJSON({ + url : kimchi.url + 'storagepools/' + sp + '/storagevolumes', + type : 'POST', + data : fd, + processData : false, + contentType : false, + success : suc, + error : err + }); + }, +
I am getting the following error while trying to upload a file into a storage pool: After some investigation I realized it is because you set the contentType as false (ie, '*/*') so Kimchi server will try to return a HTML according to the resource. In this case a Task resource. So it looks for a file: /home/alinefm/kimchi/ui/pages/Task.tmpl which does not exist and then the 404 error is reported. Check src/kimchi/template.py def can_accept_html(): return can_accept('text/html') or \ can_accept('application/xaml+xml') or \ can_accept('*/*') To solve this issue, we can change AsyncCollection (src/kimchi/control/base.py) to always return a JSON (see patch below) diff --git a/src/kimchi/control/base.py b/src/kimchi/control/base.py index 6391a1a..9ab6696 100644 --- a/src/kimchi/control/base.py +++ b/src/kimchi/control/base.py @@ -344,7 +344,10 @@ class AsyncCollection(Collection): args = self.model_args + [params] task = create(*args) cherrypy.response.status = 202 - return kimchi.template.render("Task", task) + cherrypy.response.headers['Content-Type'] = \ + 'application/json;charset=utf-8' + return json.dumps(task, indent=2, separators=(',', ':')) After applying this patch I was able to upload a file into a storage pool, but the dialog is not closed. I need to manually close the dialog and check the storage volumes inside the pool. (And any more errors on firebug =]) Let me know if you have any other idea to solve this content type issue. Otherwise, I will send the above patch for review.
+ /** + * Add a volume to a given storage pool by URL. + */ + downloadVolumeToSP: function(settings, suc, err) { + var url = settings['url']; + var name = settings['name']; + var sp = encodeURIComponent(settings['sp']); + kimchi.requestJSON({ + url : kimchi.url + 'storagepools/' + sp + '/storagevolumes', + type : 'POST', + data : JSON.stringify({ + name: name, + url: url + }), + contentType : 'application/json', + dataType : 'json', + success : suc, + error : err + }); } }; diff --git a/ui/js/src/kimchi.sp_add_volume_main.js b/ui/js/src/kimchi.sp_add_volume_main.js new file mode 100644 index 0000000..e4f25fc --- /dev/null +++ b/ui/js/src/kimchi.sp_add_volume_main.js
Name the fie as storagepool_add_volume_main.js so we use the same pattern already in use.
@@ -0,0 +1,152 @@ +/* + * Project Kimchi + * + * Copyright IBM, Corp. 2013-2014 + * + * Licensed 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. + */ +kimchi.sp_add_volume_main = function() { + // download from remote server or upload from local file + var type = 'download'; + + var addButton = $('#sp-add-volume-button'); + + $('input.volume-type').change(function(e) { + $('.volume-input').prop('disabled', true); + $('.volume-input.' + this.value).prop('disabled', false); + type = this.value; + }); + + var volumeName; + var taskID = -1; + + var onAccepted = function() { + if(inited[taskID]) { + return; + } + inited[taskID] = true; + kimchi.window.close(); + + kimchi.topic('kimchi/volumeDownloadStarted').publish({ + sp: kimchi.selectedSP + }); + }; + var inited = {}; + + var onError = function(result) { + if (result['message']) { + var errText = result['message']; + } + else { + var errText = result['responseJSON']['reason']; + } + result && kimchi.message.error(errText); + var volumeName = result['target_uri'].split('/').pop(); + var volumeBox = $('#volume' + kimchi.selectedSP + ' [data-volume-name="' + volumeName + '"]'); + $('.progress-status', volumeBox).text(i18n['KCHPOOL6016M']); + }; + + var fetchRemoteFile = function() { + var volumeURL = $('#volume-remote-url').val(); + volumeName = volumeURL.split(/(\\|\/)/g).pop(); + kimchi.downloadVolumeToSP({ + sp: kimchi.selectedSP, + name: volumeName, + url: volumeURL + }, function(resp) { + taskID = resp['id']; + onAccepted(); + }, onError); + }; + + var onProgress = function(resp) { + var sizeArray = resp['message'].split('/'); + var downloaded = sizeArray[0]; + var percent = 0; + if(isNaN(downloaded)) { + downloaded = 0; + } + else { + var total = sizeArray[1]; + percent = downloaded / total * 100; + } + var formatted = kimchi.formatMeasurement(downloaded); + var size = formatted['v'].toFixed(1) + formatted['s']; + var volumeBox = $('#volume' + kimchi.selectedSP + ' [data-volume-name="' + volumeName + '"]'); + $('.volume-progress', volumeBox).removeClass('hidden'); + $('.progress-bar-inner', volumeBox).css({ + width: percent + '%' + }); + $('.progress-status', volumeBox).text(i18n['KCHPOOL6014M']); + $('.progress-downloaded', volumeBox).text(size); + }; + + var onTaskResponse = function(result) { + var taskStatus = result['status']; + switch(taskStatus) { + case 'running': + onProgress(result); + setTimeout(function() { + trackTask(result['id']); + }, 1500); + break; + case 'finished': + taskID = -1; + var volumeName = result['target_url'].split('/').pop();
It should be target_uri
+ var volumeBox = $('#volume' + kimchi.selectedSP + ' [data-volume-name="' + volumeName + '"]'); + $('.progress-status', volumeBox).text(i18n['KCHPOOL6015M']); + break; + case 'failed': + taskID = -1; + onError(result); + break; + default: + break; + } + }; + + var trackTask = function(task) { + kimchi.getTask(task, onTaskResponse, function(resp) {}); + }; + + var uploadFile = function() { + var blobFile = $('#volume-input-file')[0].files[0]; + var fileName = blobFile.name; + var fd = new FormData(); + fd.append('name', fileName); + fd.append('file', blobFile); + kimchi.uploadVolumeToSP({ + sp: kimchi.selectedSP, + formData: fd + }, function(resp) { + }); + }; + + $(addButton).on('click', function(event) { + $(this).prop('disabled', true); + if(type === 'download') { + fetchRemoteFile(); + } + else { + uploadFile(); + } + event.preventDefault(); + }); + + kimchi.topic('kimchi/volumesListed').subscribe(function() { + var volumeBox = $('#volume' + kimchi.selectedSP + ' [data-volume-name="' + volumeName + '"]'); + $(volumeBox).removeClass('hidden'); + $('.progress-status', volumeBox).text(i18n['KCHPOOL6014M']); + taskID > -1 && trackTask(taskID); + }); +}; diff --git a/ui/js/src/kimchi.storage_main.js b/ui/js/src/kimchi.storage_main.js index ae3f963..56252a8 100644 --- a/ui/js/src/kimchi.storage_main.js +++ b/ui/js/src/kimchi.storage_main.js @@ -135,6 +135,12 @@ kimchi.storageBindClick = function() { } });
+ $('.pool-add-volume').on('click', function(event) { + var poolName = $(this).data('name'); + kimchi.selectedSP = poolName; + kimchi.window.open('sp-add-volume.html'); + }); + $('.storage-action').on('click', function() { var storage_action = $(this); var deleteButton = storage_action.find('.pool-delete'); @@ -149,10 +155,6 @@ kimchi.storageBindClick = function() { $("#logicalPoolExtend").dialog("option", "poolName", $(this).data('name')); $("#logicalPoolExtend").dialog("open"); }); - - $('#volume-doAdd').on('click', function() { - kimchi.window.open('storagevolume-add.html'); - }); }
$('.storage-li').on('click', function(event) { @@ -195,6 +197,9 @@ kimchi.doListVolumes = function(poolObj) { poolObj.removeClass('in'); kimchi.changeArrow(handleArrow); slide.slideDown('slow'); + setTimeout(function() { + kimchi.topic('kimchi/volumesListed').publish(); + }, 1500); } }, function(err) { kimchi.message.error(err.responseJSON.reason); @@ -255,6 +260,12 @@ kimchi.storage_main = function() { } kimchi.doListStoragePools(); kimchi.initLogicalPoolExtend(); + + kimchi.topic('kimchi/volumeDownloadStarted').subscribe(function(data) { + var sp = data['sp']; + var poolNode = $('.storage-li[data-name="' + sp + '"]'); + kimchi.doListVolumes(poolNode); + }); }
kimchi.changeArrow = function(obj) { diff --git a/ui/pages/i18n.json.tmpl b/ui/pages/i18n.json.tmpl index d920ae2..b3bca53 100644 --- a/ui/pages/i18n.json.tmpl +++ b/ui/pages/i18n.json.tmpl @@ -169,6 +169,9 @@ "KCHPOOL6011M": "$_("No available partitions found.")", "KCHPOOL6012M": "$_("This storage pool is not persistent. Instead of deactivate, this action will permanently delete it. Would you like to continue?")", "KCHPOOL6013M": "$_("Unable to retrieve partitions information.")", + "KCHPOOL6014M": "$_("Downloading...")", + "KCHPOOL6015M": "$_("Done!")", + "KCHPOOL6016M": "$_("Failed!")",
"KCHVMSTOR0001E": "$_("CDROM path need to be a valid local path and cannot be blank.")", "KCHVMSTOR0002E": "$_("Disk pool or volume cannot be blank.")" diff --git a/ui/pages/sp-add-volume.html.tmpl b/ui/pages/sp-add-volume.html.tmpl new file mode 100644 index 0000000..148a17b --- /dev/null +++ b/ui/pages/sp-add-volume.html.tmpl @@ -0,0 +1,80 @@ +#* + * Project Kimchi + * + * Copyright IBM, Corp. 2014 + * + * Authors: + * Hongliang Wang <hlwang@linux.vnet.ibm.com> + * + * Licensed 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. + *# +#unicode UTF-8 +#import gettext +#from kimchi.cachebust import href +#silent t = gettext.translation($lang.domain, $lang.localedir, languages=$lang.lang) +#silent _ = t.gettext +#silent _t = t.gettext +<div id="sp-add-volume-window" class="window"> + <form id="form-sp-add-volume"> + <header class="window-header"> + <h1 class="title">$_("Add a Volume to Storage Pool")</h1> + <div class="close">X</div> + </header> + <section> + <div class="content"> + <div class="form-section"> + <h2> + <input type="radio" id="volume-type-download" class="volume-type" name="volumeType" value="download" checked="checked" /> + <label for="volume-type-download"> + $_("Fetch from remote URL") + </label> + </h2> + <div class="field"> + <p class="text-help"> + $_("Enter the remote URL here.") + </p> + <div class="textbox-wrapper"> + <input type="text" id="volume-remote-url" class="text volume-input download" name="volumeRemoteURL" /> + </div> + </div> + </div> + <div class="form-section"> + <h2> + <input type="radio" id="volume-type-upload" class="volume-type" name="volumeType" value="upload"/> + <label for="volume-type-upload"> + $_("Upload an file") + </label> + </h2> + <div class="field"> + <p class="text-help"> + $_("Choose the file you want to upload.") + </p> + <div class="textbox-wrapper"> + <input type="file" class="volume-input upload" id="volume-input-file" name="volumeLocalFile" disabled="disabled" /> + </div> + </div> + </div> + </div> + </section> + <footer> + <div class="btn-group"> + <button type="submit" id="sp-add-volume-button" class="btn-normal"> + <span class="text">$_("OK")</span> + </button> + </div> + </footer> + </form> +</div> +<script type="text/javascript"> + kimchi.sp_add_volume_main(); +</script> diff --git a/ui/pages/tabs/storage.html.tmpl b/ui/pages/tabs/storage.html.tmpl index 87205bd..d5aceef 100644 --- a/ui/pages/tabs/storage.html.tmpl +++ b/ui/pages/tabs/storage.html.tmpl @@ -82,6 +82,7 @@ <div class="popover actionsheet right-side" style="width: 250px"> <button class="button-big pool-deactivate" data-stat="{state}" data-name="{name}" data-persistent="{persistent}"><span class="text">$_("Deactivate")</span></button> <button class="button-big pool-activate" data-stat="{state}" data-name="{name}"><span class="text">$_("Activate")</span></button> + <button class="button-big pool-add-volume" data-stat="{state}" data-name="{name}"><span class="text">$_("Add Volume")</span></button> <button class="button-big pool-extend {enableExt}" data-stat="{state}" data-name="{name}"><span class="text">$_("Extend")</span></button> <button class="button-big red pool-delete" data-stat="{state}" data-name="{name}"><span class="text">$_("Undefine")</span></button> </div> @@ -98,11 +99,20 @@ </li> </script> <script id="volumeTmpl" type="html/text"> - <div class="volume-box white-box"> + <div class="volume-box white-box" data-volume-name="{name}"> <div class="storage-icon volume-default icon-{format} "> </div> <div class="volume-title"> <div class="volume-name" title="{name}">{name}</div> + <div class="volume-progress hidden"> + <div class="progress-bar-outer"> + <div class="progress-bar-inner"></div> + </div> + <div class="progress-label"> + <span class="progress-status"></span> + <span class="progress-downloaded"></span> + </div> + </div> </div> <div class="volume-setting"> </div>

I am getting the following error while trying to upload a file into a storage pool:
After some investigation I realized it is because you set the contentType as false (ie, '*/*') so Kimchi server will try to return a HTML according to the resource. In this case a Task resource. So it looks for a file: /home/alinefm/kimchi/ui/pages/Task.tmpl which does not exist and then the 404 error is reported.
Check src/kimchi/template.py
def can_accept_html(): return can_accept('text/html') or \ can_accept('application/xaml+xml') or \ can_accept('*/*')
To solve this issue, we can change AsyncCollection (src/kimchi/control/base.py) to always return a JSON (see patch below)
Hey, seems the solution is really simpler than that. You just need to set dataType to json (while doing the upload request) so the response will be JSON. ;-) diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js index 766df6a..37d1c74 100644 --- a/ui/js/src/kimchi.api.js +++ b/ui/js/src/kimchi.api.js @@ -1141,6 +1141,7 @@ var kimchi = { kimchi.requestJSON({ url : kimchi.url + 'storagepools/' + sp + '/storagevolumes', type : 'POST', + dataType : 'json', data : fd, processData : false, contentType : false,

Hi! I tested this patch and here's my overall feedback: - When the button "OK" is clicked and there is some error (e.g. the URL field is empty, the URL text doesn't start with a correct protocol), the button gets disabled and it never gets enabled again unless the user closes and reopens the dialog. - When I click on the button "OK" to upload a file, the dialog doesn't close and nothing is shown. This is not the same behaviour when downloading a file: the dialog is closed immediately as soon as the submit button is clicked. - When a download finishes, the progress box doesn't update the message "Downloading...". That message should disappear and the storage volume box should look like the other existing volumes in that pool. - The correct text when uploading a file should be "Upload a file" (instead of "...an file"). - IMO the submit button's label should be a verb (e.g. "Add") instead of "OK". - IMO the menu item "Add Volume" should be the first element on the menu. The feedback related to the code is below:
- {'fc_host': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_FC_HOST, + {#'fc_host': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_FC_HOST,
If you're not using that code anymore, remove it instead of commenting it. Git keeps track of the code history.
+.volume-progress .progress-status { +} +
What's this for? It's an empty declaration.
+ var onTaskResponse = function(result) { + var taskStatus = result['status']; + switch(taskStatus) { + case 'running': + onProgress(result); + setTimeout(function() { + trackTask(result['id']); + }, 1500); + break; + case 'finished': + taskID = -1; + var volumeName = result['target_url'].split('/').pop(); + var volumeBox = $('#volume' + kimchi.selectedSP + ' [data-volume-name="' + volumeName + '"]'); + $('.progress-status', volumeBox).text(i18n['KCHPOOL6015M']); + break; + case 'failed': + taskID = -1; + onError(result); + break; + default: + break; + } + }; + + var trackTask = function(task) { + kimchi.getTask(task, onTaskResponse, function(resp) {}); + };
Does this tracking approach work for multiple simultaneous downloads/uploads?
+ + var uploadFile = function() { + var blobFile = $('#volume-input-file')[0].files[0]; + var fileName = blobFile.name; + var fd = new FormData(); + fd.append('name', fileName); + fd.append('file', blobFile); + kimchi.uploadVolumeToSP({ + sp: kimchi.selectedSP, + formData: fd + }, function(resp) { + }); Why isn't an error handler used here as well, just like the download function call?
I tested your patches on top of my [still] local branch with the patch "storagevolume: Use default value for param 'name' when appropriate" which I sent today for review on this mailing list. Without it, the download and upload functions don't work because the parameter 'name' is not passed correctly to the backend. That's why I sent this patch today.
participants (3)
-
Aline Manera
-
Crístian Viana
-
Hongliang Wang