
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>