<html>
<head>
<meta content="text/html; charset=windows-1252"
http-equiv="Content-Type">
</head>
<body text="#000000" bgcolor="#FFFFFF">
<br>
<div class="moz-cite-prefix">On 09/12/2014 11:32 PM, Aline Manera
wrote:<br>
</div>
<blockquote cite="mid:5413121C.1050804@linux.vnet.ibm.com"
type="cite">
<br>
On 09/12/2014 06:42 AM, Hongliang Wang wrote:
<br>
<blockquote type="cite">Implemented download and upload volumes
functions.
<br>
<br>
Signed-off-by: Hongliang Wang <a class="moz-txt-link-rfc2396E" href="mailto:hlwang@linux.vnet.ibm.com"><hlwang@linux.vnet.ibm.com></a>
<br>
---
<br>
ui/css/theme-default/storagepool-add-volume.css | 36 ++++
<br>
ui/js/src/kimchi.storagepool_add_volume_main.js | 243
++++++++++++++++++++++++
<br>
ui/pages/storagepool-add-volume.html.tmpl | 80 ++++++++
<br>
3 files changed, 359 insertions(+)
<br>
create mode 100644
ui/css/theme-default/storagepool-add-volume.css
<br>
create mode 100644
ui/js/src/kimchi.storagepool_add_volume_main.js
<br>
create mode 100644 ui/pages/storagepool-add-volume.html.tmpl
<br>
<br>
diff --git a/ui/css/theme-default/storagepool-add-volume.css
b/ui/css/theme-default/storagepool-add-volume.css
<br>
new file mode 100644
<br>
index 0000000..6e8a551
<br>
--- /dev/null
<br>
+++ b/ui/css/theme-default/storagepool-add-volume.css
<br>
@@ -0,0 +1,36 @@
<br>
+/*
<br>
+ * Project Kimchi
<br>
+ *
<br>
+ * Copyright IBM, Corp. 2014
<br>
+ *
<br>
+ * Licensed under the Apache License, Version 2.0 (the
"License");
<br>
+ * you may not use this file except in compliance with the
License.
<br>
+ * You may obtain a copy of the License at
<br>
+ *
<br>
+ * <a class="moz-txt-link-freetext" href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>
<br>
+ *
<br>
+ * Unless required by applicable law or agreed to in writing,
software
<br>
+ * distributed under the License is distributed on an "AS IS"
BASIS,
<br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied.
<br>
+ * See the License for the specific language governing
permissions and
<br>
+ * limitations under the License.
<br>
+ */
<br>
+#sp-add-volume-window {
<br>
+ height: 400px;
<br>
+ width: 500px;
<br>
+}
<br>
+
<br>
+#sp-add-volume-window .textbox-wrapper input[type="text"] {
<br>
+ box-sizing: border-box;
<br>
+ width: 100%;
<br>
+}
<br>
+
<br>
+#sp-add-volume-window .textbox-wrapper label {
<br>
+ vertical-align: middle;
<br>
+}
<br>
+
<br>
+#sp-add-volume-window input[type="text"][disabled] {
<br>
+ color: #bbb;
<br>
+ background-color: #fafafa;
<br>
+ cursor: not-allowed;
<br>
+}
<br>
diff --git a/ui/js/src/kimchi.storagepool_add_volume_main.js
b/ui/js/src/kimchi.storagepool_add_volume_main.js
<br>
new file mode 100644
<br>
index 0000000..9435e28
<br>
--- /dev/null
<br>
+++ b/ui/js/src/kimchi.storagepool_add_volume_main.js
<br>
@@ -0,0 +1,243 @@
<br>
+/*
<br>
+ * Project Kimchi
<br>
+ *
<br>
+ * Copyright IBM, Corp. 2014
<br>
+ *
<br>
+ * Licensed under the Apache License, Version 2.0 (the
'License');
<br>
+ * you may not use this file except in compliance with the
License.
<br>
+ * You may obtain a copy of the License at
<br>
+ *
<br>
+ * <a class="moz-txt-link-freetext" href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>
<br>
+ *
<br>
+ * Unless required by applicable law or agreed to in writing,
software
<br>
+ * distributed under the License is distributed on an 'AS IS'
BASIS,
<br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied.
<br>
+ * See the License for the specific language governing
permissions and
<br>
+ * limitations under the License.
<br>
+ */
<br>
+kimchi.sp_add_volume_main = function() {
<br>
+ // download from remote server or upload from local file
<br>
+ var type = 'download';
<br>
+
<br>
+ var addButton = $('#sp-add-volume-button');
<br>
+ var remoteURLBox = $('#volume-remote-url');
<br>
+ var localFileBox = $('#volume-input-file');
<br>
+ var typeRadios = $('input.volume-type');
<br>
+
<br>
+ var isValidURL = function() {
<br>
+ var url = $(remoteURLBox).val();
<br>
+ return kimchi.template_check_url(url);
<br>
+ };
<br>
+
<br>
</blockquote>
<br>
<br>
<blockquote type="cite">+ var isValidFile = function() {
<br>
+ var fileName = $(localFileBox).val();
<br>
+ return fileName &&
<br>
+ /[Ii][Ss][Oo]/g.test(fileName.split('.').pop());
<br>
+ };
<br>
</blockquote>
<br>
The user can download and upload any type of file.
<br>
So you just need to check it a file was selected.
<br>
</blockquote>
ACK.<br>
<blockquote cite="mid:5413121C.1050804@linux.vnet.ibm.com"
type="cite">
<br>
<blockquote type="cite">+
<br>
+ $(typeRadios).change(function(event) {
<br>
+ $('.volume-input').prop('disabled', true);
<br>
+ $('.volume-input.' + this.value).prop('disabled',
false);
<br>
+ type = this.value;
<br>
+ if(type == 'download') {
<br>
+ $(addButton).prop('disabled', !isValidURL());
<br>
+ }
<br>
+ else {
<br>
+ $(addButton).prop('disabled', !isValidFile());
<br>
+ }
<br>
+ });
<br>
+
<br>
+ $(remoteURLBox).on('input propertychange', function(event)
{
<br>
+ $(addButton).prop('disabled', !isValidURL());
<br>
+ });
<br>
+
<br>
+ $(localFileBox).on('change', function(event) {
<br>
+ $(addButton).prop('disabled', !isValidFile());
<br>
+ });
<br>
+
<br>
</blockquote>
<br>
<br>
<blockquote type="cite">+ if(!kimchi.volumeTransferTracker) {
<br>
+ kimchi.volumeTransferTracker = (function() {
<br>
+ var tasks = {},
<br>
+ sps = {};
<br>
+ var addTask = function(task) {
<br>
+ var taskID = task['id'];
<br>
+ tasks[taskID] = task;
<br>
+ var sp = task['sp'];
<br>
+ if(sps[sp] === undefined) {
<br>
+ sps[sp] = 1;
<br>
+ }
<br>
+ else {
<br>
+ sps[sp]++;
<br>
+ }
<br>
+ };
<br>
+ var getTask = function(taskID) {
<br>
+ return tasks[taskID];
<br>
+ };
<br>
+ var removeTask = function(task) {
<br>
+ var taskID = task['id'];
<br>
+ var sp = tasks[taskID]['sp'];
<br>
+ delete tasks[taskID];
<br>
+ if(--sps[sp] === 0) {
<br>
+ delete sps[sp];
<br>
+
kimchi.topic('kimchi/allVolumeTasksFinished').publish({
<br>
+ sp: sp
<br>
+ });
<br>
+ }
<br>
+ };
<br>
+ return {
<br>
+ add: addTask,
<br>
+ get: getTask,
<br>
+ remove: removeTask
<br>
+ };
<br>
+ })();
<br>
+ }
<br>
+ var taskTracker = kimchi.volumeTransferTracker;
<br>
</blockquote>
<br>
Why the above code is needed?
<br>
</blockquote>
This code is used for:<br>
1) Setup relationship between storage pool and task, and make it
possible to retrieve the task ID for a volume and the storage pool
name for a task. In current GET /tasks response, there is no storage
pool name information so it's inconvenient when we want to update
the progress information for a volume because we need to find which
storage pool is this volume is located (volumes with same name can
be located in different storage pools).<br>
2) There may be several in-progress volumes at the same time within
a same storage pool. So we need do a whole refresh of the storage
pool when all volume transferring is done. We need a counter for
each storage pools.<br>
3) Performance. Keeping the mapping of storage pool name and task in
memory can save Ajax requests to improve performance.<br>
<br>
<blockquote cite="mid:5413121C.1050804@linux.vnet.ibm.com"
type="cite">
<br>
The flow should be:
<br>
<br>
1) User selects a file to upload or download and click on "Add"
button
<br>
2) Add handler will only start the process
<br>
POST /storagepools/<pool>/storagevolumes/<volume>
{<data>}
<br>
<br>
While listing the storage volumes in a storage pool you need to:
<br>
<br>
GET /storagepools/<pool>/storagevolumes/
<br>
<br>
*AND* get the storage volumes in progress by calling:
<br>
<br>
GET
/tasks?status=running&target_uri=^/storagepools/<pool>/storagevolumes/*<br>
<br>
So when listing the volumes you will know which ones are in
progress.
<br>
<br>
volumes = GET
/storagepools/<pool>/storagevolumes/<volume>
<br>
pendingVolumes = GET
/tasks?status=running&target_uri=^/storagepools/<pool>/storagevolumes/*<br>
<br>
for volumes in volumes:
<br>
#create volume box html
<br>
<br>
# check the volume is in progress
<br>
if volume in pendingVolumes:
<br>
# add progress bar according to task
<br>
# add taskTrack for this task id
<br>
<br>
<br>
By now the Task resource can only return target_uri and message,
so there is no way to differ download from upload.
<br>
What we know is only if a storage volume is pending.
<br>
So I suggest to change the progress bar message to a generic one,
example, "In progress"
<br>
</blockquote>
ACK.<br>
Already save the download/upload information when making the Ajax
callback in <b>makeCallback()</b> function. Though if the user
refreshes the browser, the information will be lost. Then we'll lose
the transfer type information and degrade it to "In progress".<br>
<blockquote cite="mid:5413121C.1050804@linux.vnet.ibm.com"
type="cite">
<br>
<br>
<br>
<blockquote type="cite">+
<br>
+ var makeCallback = function(trackType, sp, transType,
callback) {
<br>
+ return function(resp) {
<br>
+ var taskID = resp['id'];
<br>
+ var volumeName =
resp['target_uri'].split('/').pop();
<br>
+ if(trackType === 'add') {
<br>
+ taskTracker.add({
<br>
+ id: taskID,
<br>
+ sp: sp,
<br>
+ volume: volumeName
<br>
+ });
<br>
+ }
<br>
+ callback(transType, resp);
<br>
+ };
<br>
+ };
<br>
+
<br>
+ var extractProgressData = function(data) {
<br>
+ var sizeArray = /(\d+)\/(\d+)/g.exec(data) || [0, 0,
0];
<br>
+ var downloaded = sizeArray[1];
<br>
+ var percent = 0;
<br>
+ if(downloaded) {
<br>
+ var total = sizeArray[2];
<br>
+ if(!isNaN(total)) {
<br>
+ percent = downloaded / total * 100;
<br>
+ }
<br>
+ }
<br>
+ var formatted = kimchi.formatMeasurement(downloaded);
<br>
+ var size = (1.0 * formatted['v']).toFixed(1) +
formatted['s'];
<br>
+ return {
<br>
+ size: size,
<br>
+ percent: percent
<br>
+ };
<br>
+ };
<br>
+
<br>
+ var onFinished = function(type, result) {
<br>
+ var progress = extractProgressData(resp['message']);
<br>
+ var task = taskTracker.get([result['id']]);
<br>
+
kimchi.topic('kimchi/volumeTransferFinished').publish($.extend(progress,
{
<br>
+ sp: task['sp'],
<br>
+ type: type,
<br>
+ volume: task['volume']
<br>
+ }));
<br>
+ taskTracker.remove({
<br>
+ id: result['id']
<br>
+ });
<br>
+ };
<br>
+
<br>
+ var onProgress = function(type, resp) {
<br>
+ var progress = extractProgressData(resp['message']);
<br>
+ var task = taskTracker.get([resp['id']]);
<br>
+
kimchi.topic('kimchi/volumeTransferProgress').publish($.extend(progress,
{
<br>
+ sp: task['sp'],
<br>
+ type: type,
<br>
+ volume: task['volume']
<br>
+ }));
<br>
+ };
<br>
+
<br>
+ var onTransferError = function(type, result) {
<br>
+ if(!result) {
<br>
+ return;
<br>
+ }
<br>
+ var msg = result && (result['message'] || (
<br>
+ result['responseJSON'] &&
result['responseJSON']['reason'])
<br>
+ );
<br>
+ kimchi.message.error(msg);
<br>
+
<br>
+ if(!result['target_uri']) {
<br>
+ return;
<br>
+ }
<br>
+ var task = taskTracker.get(result['id']);
<br>
+ kimchi.topic('kimchi/volumeTransferError').publish({
<br>
+ sp: task['sp'],
<br>
+ type: type,
<br>
+ volume: task['volume']
<br>
+ });
<br>
+ taskTracker.remove({
<br>
+ id: result['id']
<br>
+ });
<br>
+ };
<br>
+
<br>
+ var onAccepted = function(type, resp) {
<br>
+ var taskID = resp['id'];
<br>
+ var task = taskTracker.get(taskID);
<br>
+ kimchi.window.close();
<br>
+ kimchi.topic('kimchi/volumeTransferStarted').publish({
<br>
+ sp: task['sp'],
<br>
+ type: type,
<br>
+ volume: task['volume']
<br>
+ });
<br>
+
<br>
+ kimchi.trackTask(taskID, function(resp) {
<br>
+ onFinished(type, resp);
<br>
+ }, function(resp) {
<br>
+ onTransferError(type, resp);
<br>
+ }, function(resp) {
<br>
+ onProgress(type, resp);
<br>
+ });
<br>
+ };
<br>
+
<br>
+ var onError = function(result) {
<br>
+ $(this).prop('disabled', false);
<br>
+ $(typeRadios).prop('disabled', false);
<br>
+ if(!result) {
<br>
+ return;
<br>
+ }
<br>
+ var msg = result['message'] || (
<br>
+ result['responseJSON'] &&
result['responseJSON']['reason']
<br>
+ );
<br>
+ kimchi.message.error(msg);
<br>
+ };
<br>
+
<br>
+ var fetchRemoteFile = function() {
<br>
+ var volumeURL = remoteURLBox.val();
<br>
+ var volumeName = volumeURL.split(/(\\|\/)/g).pop();
<br>
+ kimchi.downloadVolumeToSP({
<br>
+ sp: kimchi.selectedSP,
<br>
+ name: volumeName,
<br>
+ url: volumeURL
<br>
+ }, makeCallback('add', kimchi.selectedSP, 'download',
onAccepted),
<br>
+ onError
<br>
+ );
<br>
+ };
<br>
+
<br>
</blockquote>
<br>
<br>
<blockquote type="cite">+ var uploadFile = function() {
<br>
+ var blobFile = $(localFileBox)[0].files[0];
<br>
+ var fileName = blobFile.name;
<br>
+ var fd = new FormData();
<br>
+ fd.append('name', fileName);
<br>
+ fd.append('file', blobFile);
<br>
+ kimchi.uploadVolumeToSP({
<br>
+ sp: kimchi.selectedSP,
<br>
+ formData: fd
<br>
+ }, makeCallback('add', kimchi.selectedSP, 'upload',
onAccepted),
<br>
+ onError
<br>
+ );
<br>
+ };
<br>
+
<br>
</blockquote>
<br>
When selecting a file to upload the UI will need to read the
selected file to build the request.
<br>
If the file is large it takes some time to complete and the UI
freezes.
<br>
<br>
I have to suggestions:
<br>
<br>
OPTION 1:
<br>
<br>
1) Use select a file to upload
<br>
2) Close the add volume window and display the volume in progress
with the progress bar label "Verifying file"
<br>
3) Once the request is built, send it and update the volume box
accordingly
<br>
<br>
OPTION 2:
<br>
<br>
1) Use select a file to upload
<br>
2) Disable "Add" button and rename it to: "Verifying file"
<br>
3) Once the request is built, send it and close the add volume
window.
<br>
4) Update volume list
<br>
</blockquote>
ACK. Thanks for the testing for large files which I didn't. Option 1
sounds good.<br>
<blockquote cite="mid:5413121C.1050804@linux.vnet.ibm.com"
type="cite">
<br>
<blockquote type="cite">+ $(addButton).on('click',
function(event) {
<br>
+ $(this).prop('disabled', true);
<br>
+ $(typeRadios).prop('disabled', true);
<br>
+ if(type === 'download') {
<br>
+ fetchRemoteFile();
<br>
+ }
<br>
+ else {
<br>
+ uploadFile();
<br>
+ }
<br>
+ event.preventDefault();
<br>
+ });
<br>
+};
<br>
diff --git a/ui/pages/storagepool-add-volume.html.tmpl
b/ui/pages/storagepool-add-volume.html.tmpl
<br>
new file mode 100644
<br>
index 0000000..b01c942
<br>
--- /dev/null
<br>
+++ b/ui/pages/storagepool-add-volume.html.tmpl
<br>
@@ -0,0 +1,80 @@
<br>
+#*
<br>
+ * Project Kimchi
<br>
+ *
<br>
+ * Copyright IBM, Corp. 2014
<br>
+ *
<br>
+ * Authors:
<br>
+ * Hongliang Wang <a class="moz-txt-link-rfc2396E" href="mailto:hlwang@linux.vnet.ibm.com"><hlwang@linux.vnet.ibm.com></a>
<br>
+ *
<br>
+ * Licensed under the Apache License, Version 2.0 (the
"License");
<br>
+ * you may not use this file except in compliance with the
License.
<br>
+ * You may obtain a copy of the License at
<br>
+ *
<br>
+ * <a class="moz-txt-link-freetext" href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>
<br>
+ *
<br>
+ * Unless required by applicable law or agreed to in writing,
software
<br>
+ * distributed under the License is distributed on an "AS IS"
BASIS,
<br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied.
<br>
+ * See the License for the specific language governing
permissions and
<br>
+ * limitations under the License.
<br>
+ *#
<br>
+#unicode UTF-8
<br>
+#import gettext
<br>
+#from kimchi.cachebust import href
<br>
+#silent t = gettext.translation($lang.domain, $lang.localedir,
languages=$lang.lang)
<br>
+#silent _ = t.gettext
<br>
+#silent _t = t.gettext
<br>
+<div id="sp-add-volume-window" class="window">
<br>
+ <form id="form-sp-add-volume">
<br>
+ <header class="window-header">
<br>
+ <h1 class="title">$_("Add a Volume to Storage
Pool")</h1>
<br>
+ <div class="close">X</div>
<br>
+ </header>
<br>
+ <section>
<br>
+ <div class="content">
<br>
+ <div class="form-section">
<br>
+ <h2>
<br>
+ <input type="radio"
id="volume-type-download" class="volume-type" name="volumeType"
value="download" checked="checked" />
<br>
+ <label
for="volume-type-download">
<br>
+ $_("Fetch from remote URL")
<br>
+ </label>
<br>
+ </h2>
<br>
+ <div class="field">
<br>
+ <p class="text-help">
<br>
+ $_("Enter the remote URL here.")
<br>
+ </p>
<br>
+ <div class="textbox-wrapper">
<br>
+ <input type="text"
id="volume-remote-url" class="text volume-input download"
name="volumeRemoteURL" />
<br>
+ </div>
<br>
+ </div>
<br>
+ </div>
<br>
+ <div class="form-section">
<br>
+ <h2>
<br>
+ <input type="radio"
id="volume-type-upload" class="volume-type" name="volumeType"
value="upload"/>
<br>
+ <label for="volume-type-upload">
<br>
+ $_("Upload an file")
<br>
+ </label>
<br>
+ </h2>
<br>
+ <div class="field">
<br>
+ <p class="text-help">
<br>
+ $_("Choose the ISO file (with .iso
suffix) you want to upload.")
<br>
+ </p>
<br>
+ <div class="textbox-wrapper">
<br>
+ <input type="file"
class="volume-input upload" id="volume-input-file"
name="volumeLocalFile" disabled="disabled" />
<br>
+ </div>
<br>
+ </div>
<br>
+ </div>
<br>
+ </div>
<br>
+ </section>
<br>
+ <footer>
<br>
+ <div class="btn-group">
<br>
+ <button type="submit"
id="sp-add-volume-button" class="btn-normal"
disabled="disabled">
<br>
+ <span
class="text">$_("Add")</span>
<br>
+ </button>
<br>
+ </div>
<br>
+ </footer>
<br>
+ </form>
<br>
+</div>
<br>
+<script type="text/javascript">
<br>
+ kimchi.sp_add_volume_main();
<br>
+</script>
<br>
</blockquote>
<br>
</blockquote>
<br>
</body>
</html>