<html>
<head>
<meta content="text/html; charset=ISO-8859-1"
http-equiv="Content-Type">
</head>
<body bgcolor="#FFFFFF" text="#000000">
<br>
<div class="moz-cite-prefix">On 09/05/2014 02:48 PM, Hongliang Wang
wrote:<br>
</div>
<blockquote
cite="mid:1409939286-21989-1-git-send-email-hlwang@linux.vnet.ibm.com"
type="cite">
<pre wrap="">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 <a class="moz-txt-link-rfc2396E" href="mailto:hlwang@linux.vnet.ibm.com"><hlwang@linux.vnet.ibm.com></a>
---
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,</pre>
</blockquote>
<br>
I think you used it only for your tests, right? =)<br>
<br>
<blockquote
cite="mid:1409939286-21989-1-git-send-email-hlwang@linux.vnet.ibm.com"
type="cite">
<pre wrap="">
'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
+ *</pre>
</blockquote>
<br>
The copyright refers to the year the file was created followed by
the modifications.<br>
So if a file was created in 2014 and never modified the copyright
will be only 2014.<br>
if the files was created in 2013 and modified in 2014 the copyright
will be 2013-2014<br>
So in this case you need to keep only 2014<br>
<br>
<blockquote
cite="mid:1409939286-21989-1-git-send-email-hlwang@linux.vnet.ibm.com"
type="cite">
<pre wrap="">
+ * 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
+ *
+ * <a class="moz-txt-link-freetext" href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>
+ *
+ * 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
+ });
+ },
+</pre>
</blockquote>
<br>
I am getting the following error while trying to upload a file into
a storage pool:<br>
<br>
<br>
<img src="cid:part1.03090309.08020406@linux.vnet.ibm.com" alt=""><br>
<br>
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.<br>
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.<br>
<br>
Check src/kimchi/template.py<br>
<br>
def
can_accept_html():
<br>
return can_accept('text/html') or
\ <br>
can_accept('application/xaml+xml') or
\ <br>
can_accept('*/*') <br>
<br>
To solve this issue, we can change AsyncCollection
(src/kimchi/control/base.py) to always return a JSON (see patch
below)<br>
<br>
<br>
diff --git a/src/kimchi/control/base.py b/src/kimchi/control/base.py<br>
index 6391a1a..9ab6696 100644<br>
--- a/src/kimchi/control/base.py<br>
+++ b/src/kimchi/control/base.py<br>
@@ -344,7 +344,10 @@ class AsyncCollection(Collection):<br>
args = self.model_args + [params]<br>
task = create(*args)<br>
cherrypy.response.status = 202<br>
- return kimchi.template.render("Task", task)<br>
+ cherrypy.response.headers['Content-Type'] = \<br>
+ 'application/json;charset=utf-8'<br>
+ return json.dumps(task, indent=2, separators=(',', ':'))<br>
<br>
<br>
After applying this patch I was able to upload a file into a storage
pool, but the dialog is not closed.<br>
I need to manually close the dialog and check the storage volumes
inside the pool. (And any more errors on firebug =])<br>
<br>
Let me know if you have any other idea to solve this content type
issue. Otherwise, I will send the above patch for review.<br>
<br>
<blockquote
cite="mid:1409939286-21989-1-git-send-email-hlwang@linux.vnet.ibm.com"
type="cite">
<pre wrap="">
+ /**
+ * 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</pre>
</blockquote>
<br>
Name the fie as storagepool_add_volume_main.js so we use the same
pattern already in use.<br>
<br>
<blockquote
cite="mid:1409939286-21989-1-git-send-email-hlwang@linux.vnet.ibm.com"
type="cite">
<pre wrap="">
@@ -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
+ *
+ * <a class="moz-txt-link-freetext" href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>
+ *
+ * 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();</pre>
</blockquote>
<br>
It should be target_uri<br>
<br>
<blockquote
cite="mid:1409939286-21989-1-git-send-email-hlwang@linux.vnet.ibm.com"
type="cite">
<pre wrap="">
+ 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 <a class="moz-txt-link-rfc2396E" href="mailto:hlwang@linux.vnet.ibm.com"><hlwang@linux.vnet.ibm.com></a>
+ *
+ * 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
+ *
+ * <a class="moz-txt-link-freetext" href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>
+ *
+ * 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>
</pre>
</blockquote>
<br>
</body>
</html>