[Kimchi-devel] [PATCH v2 3/4] Storage Pool Add Volume UI: Implement Download/Upload Volume Function
Aline Manera
alinefm at linux.vnet.ibm.com
Mon Sep 15 19:45:17 UTC 2014
On 09/15/2014 04:46 AM, Hongliang Wang wrote:
>
> On 09/12/2014 11:32 PM, Aline Manera wrote:
>>
>> On 09/12/2014 06:42 AM, Hongliang Wang wrote:
>>> Implemented download and upload volumes functions.
>>>
>>> Signed-off-by: Hongliang Wang <hlwang at linux.vnet.ibm.com>
>>> ---
>>> ui/css/theme-default/storagepool-add-volume.css | 36 ++++
>>> ui/js/src/kimchi.storagepool_add_volume_main.js | 243
>>> ++++++++++++++++++++++++
>>> ui/pages/storagepool-add-volume.html.tmpl | 80 ++++++++
>>> 3 files changed, 359 insertions(+)
>>> create mode 100644 ui/css/theme-default/storagepool-add-volume.css
>>> create mode 100644 ui/js/src/kimchi.storagepool_add_volume_main.js
>>> create mode 100644 ui/pages/storagepool-add-volume.html.tmpl
>>>
>>> diff --git a/ui/css/theme-default/storagepool-add-volume.css
>>> b/ui/css/theme-default/storagepool-add-volume.css
>>> new file mode 100644
>>> index 0000000..6e8a551
>>> --- /dev/null
>>> +++ b/ui/css/theme-default/storagepool-add-volume.css
>>> @@ -0,0 +1,36 @@
>>> +/*
>>> + * Project Kimchi
>>> + *
>>> + * Copyright IBM, Corp. 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/js/src/kimchi.storagepool_add_volume_main.js
>>> b/ui/js/src/kimchi.storagepool_add_volume_main.js
>>> new file mode 100644
>>> index 0000000..9435e28
>>> --- /dev/null
>>> +++ b/ui/js/src/kimchi.storagepool_add_volume_main.js
>>> @@ -0,0 +1,243 @@
>>> +/*
>>> + * Project Kimchi
>>> + *
>>> + * Copyright IBM, Corp. 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');
>>> + var remoteURLBox = $('#volume-remote-url');
>>> + var localFileBox = $('#volume-input-file');
>>> + var typeRadios = $('input.volume-type');
>>> +
>>> + var isValidURL = function() {
>>> + var url = $(remoteURLBox).val();
>>> + return kimchi.template_check_url(url);
>>> + };
>>> +
>>
>>
>>> + var isValidFile = function() {
>>> + var fileName = $(localFileBox).val();
>>> + return fileName &&
>>> + /[Ii][Ss][Oo]/g.test(fileName.split('.').pop());
>>> + };
>>
>> The user can download and upload any type of file.
>> So you just need to check it a file was selected.
> ACK.
>>
>>> +
>>> + $(typeRadios).change(function(event) {
>>> + $('.volume-input').prop('disabled', true);
>>> + $('.volume-input.' + this.value).prop('disabled', false);
>>> + type = this.value;
>>> + if(type == 'download') {
>>> + $(addButton).prop('disabled', !isValidURL());
>>> + }
>>> + else {
>>> + $(addButton).prop('disabled', !isValidFile());
>>> + }
>>> + });
>>> +
>>> + $(remoteURLBox).on('input propertychange', function(event) {
>>> + $(addButton).prop('disabled', !isValidURL());
>>> + });
>>> +
>>> + $(localFileBox).on('change', function(event) {
>>> + $(addButton).prop('disabled', !isValidFile());
>>> + });
>>> +
>>
>>
>>> + if(!kimchi.volumeTransferTracker) {
>>> + kimchi.volumeTransferTracker = (function() {
>>> + var tasks = {},
>>> + sps = {};
>>> + var addTask = function(task) {
>>> + var taskID = task['id'];
>>> + tasks[taskID] = task;
>>> + var sp = task['sp'];
>>> + if(sps[sp] === undefined) {
>>> + sps[sp] = 1;
>>> + }
>>> + else {
>>> + sps[sp]++;
>>> + }
>>> + };
>>> + var getTask = function(taskID) {
>>> + return tasks[taskID];
>>> + };
>>> + var removeTask = function(task) {
>>> + var taskID = task['id'];
>>> + var sp = tasks[taskID]['sp'];
>>> + delete tasks[taskID];
>>> + if(--sps[sp] === 0) {
>>> + delete sps[sp];
>>> + kimchi.topic('kimchi/allVolumeTasksFinished').publish({
>>> + sp: sp
>>> + });
>>> + }
>>> + };
>>> + return {
>>> + add: addTask,
>>> + get: getTask,
>>> + remove: removeTask
>>> + };
>>> + })();
>>> + }
>>> + var taskTracker = kimchi.volumeTransferTracker;
>>
>> Why the above code is needed?
> This code is used for:
> 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).
> 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.
> 3) Performance. Keeping the mapping of storage pool name and task in
> memory can save Ajax requests to improve performance.
All that information is hold by backend in the Task element.
When querying a task related to storage volumes you should do:
GET
/tasks?status=running&target_uri=^/storagepools/<pool-name>/storagevolumes/*
This will return a list of running tasks related to storage volumes
{ id: 1,
status: running,
message: ...,
target_uri: /storagepools/default/storagevolumes/new-vol
}
...
With the target_uri parameter you can know the pool and the new volume name.
I have done that design on the patch "[Kimchi-devel] [PATCH] Adjustments
on upload/download UI"
With this approach every user logged into kimchi will have the same view
of volumes in progress.
>
>>
>> The flow should be:
>>
>> 1) User selects a file to upload or download and click on "Add" button
>> 2) Add handler will only start the process
>> POST /storagepools/<pool>/storagevolumes/<volume> {<data>}
>>
>> While listing the storage volumes in a storage pool you need to:
>>
>> GET /storagepools/<pool>/storagevolumes/
>>
>> *AND* get the storage volumes in progress by calling:
>>
>> GET
>> /tasks?status=running&target_uri=^/storagepools/<pool>/storagevolumes/*
>>
>> So when listing the volumes you will know which ones are in progress.
>>
>> volumes = GET /storagepools/<pool>/storagevolumes/<volume>
>> pendingVolumes = GET
>> /tasks?status=running&target_uri=^/storagepools/<pool>/storagevolumes/*
>>
>> for volumes in volumes:
>> #create volume box html
>>
>> # check the volume is in progress
>> if volume in pendingVolumes:
>> # add progress bar according to task
>> # add taskTrack for this task id
>>
>>
>> By now the Task resource can only return target_uri and message, so
>> there is no way to differ download from upload.
>> What we know is only if a storage volume is pending.
>> So I suggest to change the progress bar message to a generic one,
>> example, "In progress"
> ACK.
> Already save the download/upload information when making the Ajax
> callback in *makeCallback()* 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".
We should use the same "In progress" message in all cases so every user
will have the same view on it.
>>
>>
>>
>>> +
>>> + var makeCallback = function(trackType, sp, transType, callback) {
>>> + return function(resp) {
>>> + var taskID = resp['id'];
>>> + var volumeName = resp['target_uri'].split('/').pop();
>>> + if(trackType === 'add') {
>>> + taskTracker.add({
>>> + id: taskID,
>>> + sp: sp,
>>> + volume: volumeName
>>> + });
>>> + }
>>> + callback(transType, resp);
>>> + };
>>> + };
>>> +
>>> + var extractProgressData = function(data) {
>>> + var sizeArray = /(\d+)\/(\d+)/g.exec(data) || [0, 0, 0];
>>> + var downloaded = sizeArray[1];
>>> + var percent = 0;
>>> + if(downloaded) {
>>> + var total = sizeArray[2];
>>> + if(!isNaN(total)) {
>>> + percent = downloaded / total * 100;
>>> + }
>>> + }
>>> + var formatted = kimchi.formatMeasurement(downloaded);
>>> + var size = (1.0 * formatted['v']).toFixed(1) + formatted['s'];
>>> + return {
>>> + size: size,
>>> + percent: percent
>>> + };
>>> + };
>>> +
>>> + var onFinished = function(type, result) {
>>> + var progress = extractProgressData(resp['message']);
>>> + var task = taskTracker.get([result['id']]);
>>> +
>>> kimchi.topic('kimchi/volumeTransferFinished').publish($.extend(progress,
>>> {
>>> + sp: task['sp'],
>>> + type: type,
>>> + volume: task['volume']
>>> + }));
>>> + taskTracker.remove({
>>> + id: result['id']
>>> + });
>>> + };
>>> +
>>> + var onProgress = function(type, resp) {
>>> + var progress = extractProgressData(resp['message']);
>>> + var task = taskTracker.get([resp['id']]);
>>> +
>>> kimchi.topic('kimchi/volumeTransferProgress').publish($.extend(progress,
>>> {
>>> + sp: task['sp'],
>>> + type: type,
>>> + volume: task['volume']
>>> + }));
>>> + };
>>> +
>>> + var onTransferError = function(type, result) {
>>> + if(!result) {
>>> + return;
>>> + }
>>> + var msg = result && (result['message'] || (
>>> + result['responseJSON'] &&
>>> result['responseJSON']['reason'])
>>> + );
>>> + kimchi.message.error(msg);
>>> +
>>> + if(!result['target_uri']) {
>>> + return;
>>> + }
>>> + var task = taskTracker.get(result['id']);
>>> + kimchi.topic('kimchi/volumeTransferError').publish({
>>> + sp: task['sp'],
>>> + type: type,
>>> + volume: task['volume']
>>> + });
>>> + taskTracker.remove({
>>> + id: result['id']
>>> + });
>>> + };
>>> +
>>> + var onAccepted = function(type, resp) {
>>> + var taskID = resp['id'];
>>> + var task = taskTracker.get(taskID);
>>> + kimchi.window.close();
>>> + kimchi.topic('kimchi/volumeTransferStarted').publish({
>>> + sp: task['sp'],
>>> + type: type,
>>> + volume: task['volume']
>>> + });
>>> +
>>> + kimchi.trackTask(taskID, function(resp) {
>>> + onFinished(type, resp);
>>> + }, function(resp) {
>>> + onTransferError(type, resp);
>>> + }, function(resp) {
>>> + onProgress(type, resp);
>>> + });
>>> + };
>>> +
>>> + var onError = function(result) {
>>> + $(this).prop('disabled', false);
>>> + $(typeRadios).prop('disabled', false);
>>> + if(!result) {
>>> + return;
>>> + }
>>> + var msg = result['message'] || (
>>> + result['responseJSON'] && result['responseJSON']['reason']
>>> + );
>>> + kimchi.message.error(msg);
>>> + };
>>> +
>>> + var fetchRemoteFile = function() {
>>> + var volumeURL = remoteURLBox.val();
>>> + var volumeName = volumeURL.split(/(\\|\/)/g).pop();
>>> + kimchi.downloadVolumeToSP({
>>> + sp: kimchi.selectedSP,
>>> + name: volumeName,
>>> + url: volumeURL
>>> + }, makeCallback('add', kimchi.selectedSP, 'download',
>>> onAccepted),
>>> + onError
>>> + );
>>> + };
>>> +
>>
>>
>>> + var uploadFile = function() {
>>> + var blobFile = $(localFileBox)[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
>>> + }, makeCallback('add', kimchi.selectedSP, 'upload',
>>> onAccepted),
>>> + onError
>>> + );
>>> + };
>>> +
>>
>> When selecting a file to upload the UI will need to read the selected
>> file to build the request.
>> If the file is large it takes some time to complete and the UI freezes.
>>
>> I have to suggestions:
>>
>> OPTION 1:
>>
>> 1) Use select a file to upload
>> 2) Close the add volume window and display the volume in progress
>> with the progress bar label "Verifying file"
>> 3) Once the request is built, send it and update the volume box
>> accordingly
>>
>> OPTION 2:
>>
>> 1) Use select a file to upload
>> 2) Disable "Add" button and rename it to: "Verifying file"
>> 3) Once the request is built, send it and close the add volume window.
>> 4) Update volume list
> ACK. Thanks for the testing for large files which I didn't. Option 1
> sounds good.
>>
>>> + $(addButton).on('click', function(event) {
>>> + $(this).prop('disabled', true);
>>> + $(typeRadios).prop('disabled', true);
>>> + if(type === 'download') {
>>> + fetchRemoteFile();
>>> + }
>>> + else {
>>> + uploadFile();
>>> + }
>>> + event.preventDefault();
>>> + });
>>> +};
>>> diff --git a/ui/pages/storagepool-add-volume.html.tmpl
>>> b/ui/pages/storagepool-add-volume.html.tmpl
>>> new file mode 100644
>>> index 0000000..b01c942
>>> --- /dev/null
>>> +++ b/ui/pages/storagepool-add-volume.html.tmpl
>>> @@ -0,0 +1,80 @@
>>> +#*
>>> + * Project Kimchi
>>> + *
>>> + * Copyright IBM, Corp. 2014
>>> + *
>>> + * Authors:
>>> + * Hongliang Wang <hlwang at 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 ISO file (with .iso
>>> suffix) 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" disabled="disabled">
>>> + <span class="text">$_("Add")</span>
>>> + </button>
>>> + </div>
>>> + </footer>
>>> + </form>
>>> +</div>
>>> +<script type="text/javascript">
>>> + kimchi.sp_add_volume_main();
>>> +</script>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.ovirt.org/pipermail/kimchi-devel/attachments/20140915/c2653d5a/attachment.html>
More information about the Kimchi-devel
mailing list