[Kimchi-devel] [PATCH V2] Add nfs server and target UI in create storage pool

Aline Manera alinefm at linux.vnet.ibm.com
Thu Jan 2 16:37:40 UTC 2014


Some comments:

1) Seems there are tabs in this patch.
    Please, always use 4 spaces to indentation

2) There are whitespaces in there too

alinefm at alinefm:~/kimchi$ git am ../mail-patches/\[Kimchi-devel\]\ 
\[PATCH\ V2\]\ Add\ nfs\ server\ and\ target\ UI\ in\ create\ storage\ 
pool.eml
Applying: Add nfs server and target UI in create storage pool
/home/alinefm/kimchi/.git/rebase-apply/patch:490: trailing whitespace.
             });
warning: 1 line adds whitespace errors.

3) About UI

I'd suggest to use a single combo box instead of the select box.
The combo box will display one option to input the server manually and 
all servers.

  -------------------
| Input new server  |
  -------------------
| host A            |
| host B            |
  -------------------

The option "Input new server" will be the default one.
When this option is selected the input box (below the combo box) will be 
enabled to user input the server information.
When other option is selected (any host IP) the input box is disabled.

What do you think about it?


On 12/30/2013 07:42 AM, zhoumeina wrote:
> v1-v2 Fix some bug in this patch, make it working and retest
> This patch is working adding a select box in create nfs pool,
> when user don't want to input the server ip or name, he can
> simply select "used the server I have used before" and choose
> a server in a select box, this is more convinent.
>
> Add a new jquery filter-select widget
>
> In order to show server target I did a select box in UI, but sometimes
> maybe the export path number is large. So I made a filter-select
> widget to make user easier to choose the export path he want.
> This is the first jquery widget. And let us make all common widget to
> jquery widget from now on.
>
> Signed-off-by: zhoumeina <zhoumein at linux.vnet.ibm.com>
> ---
>   ui/css/theme-default/button.css          |   11 +--
>   ui/css/theme-default/form.css            |    6 ++
>   ui/css/theme-default/storage.css         |    4 +-
>   ui/js/Makefile.am                        |    4 +-
>   ui/js/src/kimchi.api.js                  |   26 ++++++-
>   ui/js/src/kimchi.storagepool_add_main.js |   73 +++++++++++++++--
>   ui/js/src/kimchi.utils.js                |   11 +++
>   ui/js/widgets/filter-select.js           |  137 ++++++++++++++++++++++++++++++
>   ui/js/widgets/select-menu.js             |   86 +++++++++++++++++++
>   ui/pages/i18n.html.tmpl                  |    5 +-
>   ui/pages/storagepool-add.html.tmpl       |   41 +++++++--
>   11 files changed, 376 insertions(+), 28 deletions(-)
>   create mode 100644 ui/js/widgets/filter-select.js
>   create mode 100644 ui/js/widgets/select-menu.js
>
> diff --git a/ui/css/theme-default/button.css b/ui/css/theme-default/button.css
> index c7ed3f6..f99679a 100644
> --- a/ui/css/theme-default/button.css
> +++ b/ui/css/theme-default/button.css
> @@ -247,17 +247,16 @@
>   .btn-select {
>       display: inline-block;
>       position: relative;
> -    height: 38px;
> +    height: 30px;
>       padding-right: 20px;
>       margin: 5px;
>       vertical-align: top;
>       -webkit-border-radius: 5px;
>       -moz-border-radius: 5px;
> -    border-radius: 5px;
> -    background: #eee;
> +    background: #fff;
>       box-shadow: -1px -1px 1px #666, 1px 1px 1px #fff, 2px 2px 2px rgba(0, 0, 0, .15) inset;
>       font-size: 13px;
> -    line-height: 38px;
> +    line-height: 30px;
>       text-align: left;
>       cursor: pointer;
>   }
> @@ -269,8 +268,8 @@
>   .btn-select .arrow {
>       position: absolute;
>       width: 15px;
> -    height: 38px;
> -    line-height: 38px;
> +    height: 30px;
> +    line-height: 30px;
>       top: 0;
>       right: 5px;
>       background: url(../images/theme-default/arrow-down-black.png) no-repeat center center;
> diff --git a/ui/css/theme-default/form.css b/ui/css/theme-default/form.css
> index c24b277..9db4bba 100644
> --- a/ui/css/theme-default/form.css
> +++ b/ui/css/theme-default/form.css
> @@ -45,3 +45,9 @@
>       line-height: 30px;
>       padding: 0 5px;
>   }
> +
> +.text-help {
> +	font-size: 12px;
> +    color: #333;
> +    margin: 0 0 5px 5px;
> +}
> diff --git a/ui/css/theme-default/storage.css b/ui/css/theme-default/storage.css
> index d81dc75..ae89f1b 100644
> --- a/ui/css/theme-default/storage.css
> +++ b/ui/css/theme-default/storage.css
> @@ -529,7 +529,7 @@
>       width: 300px;
>       display: inline-block;
>       vertical-align: top;
> -    padding: 5px 5px 5px 20px;
> +    padding: 5px 5px 5px 22px;
>   }
>
>   .storage-type-wrapper-controls input[type="text"] {
> @@ -548,7 +548,7 @@
>
>   .storage-type-wrapper-controls > .dropdown {
>       margin: 5px 0 0 1px;
> -    width: 250px;
> +    width: 150px;
>   }
>
>   .storage-type-wrapper-controls input[type="text"][disabled] {
> diff --git a/ui/js/Makefile.am b/ui/js/Makefile.am
> index 337e369..8dfd4d6 100644
> --- a/ui/js/Makefile.am
> +++ b/ui/js/Makefile.am
> @@ -20,7 +20,7 @@
>
>   SUBDIRS = novnc
>
> -EXTRA_DIST = src
> +EXTRA_DIST = src widgets
>
>   jsdir = $(datadir)/kimchi/ui/js
>
> @@ -32,7 +32,7 @@ dist_js_DATA = \
>   	modernizr.custom.2.6.2.min.js \
>   	$(NULL)
>
> -kimchi.min.js: src/*.js
> +kimchi.min.js: widgets/*.js src/*.js
>   	cat $(sort $^) > $@
>
>   CLEANFILES = kimchi.min.js
> diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js
> index fbcf4a2..f6115a8 100644
> --- a/ui/js/src/kimchi.api.js
> +++ b/ui/js/src/kimchi.api.js
> @@ -681,5 +681,29 @@ var kimchi = {
>               success : suc,
>               error : err
>           });
> +    },
> +
> +    getStorageServers: function(type, suc, err) {
> +        var url = kimchi.url + 'storageservers?target_type=' + type;
> +        kimchi.requestJSON({
> +            url : url,
> +            type : 'GET',
> +            contentType : 'application/json',
> +            dataType : 'json',
> +            success : suc,
> +            error : err
> +        });
> +    },
> +
> +    getStorageTargets: function(server,type, suc, err) {
> +        var url = kimchi.url + 'storageservers/' + server + '/storagetargets?target_type=' + type;
> +        kimchi.requestJSON({
> +            url : url,
> +            type : 'GET',
> +            contentType : 'application/json',
> +            dataType : 'json',
> +            success : suc,
> +            error : err
> +        });
>       }
> -};
> +};
> \ No newline at end of file
> diff --git a/ui/js/src/kimchi.storagepool_add_main.js b/ui/js/src/kimchi.storagepool_add_main.js
> index b31610a..7955352 100644
> --- a/ui/js/src/kimchi.storagepool_add_main.js
> +++ b/ui/js/src/kimchi.storagepool_add_main.js
> @@ -51,7 +51,72 @@ kimchi.initStorageAddPage = function() {
>               });
>               $('.host-partition').html(listHtml);
>           }
> -        kimchi.select('storagePool-list', options);
> +        $('#storagePool-list').selectMenu();
> +        $('#storagePool-list').selectMenu("setData", options);
> +        kimchi.getStorageServers('netfs', function(data) {
> +            var serverContent = [];
> +            serverContent.push({
> +                label : i18n['select_default'],
> +                value : ''
> +            });
> +            if (data.length > 0) {
> +                $.each(data, function(index, value) {
> +                    serverContent.push({
> +                        label : value,
> +                        value : value
> +                    });
> +                });
> +            }
> +            $('#nfs-server-used').selectMenu();
> +            $('#nfs-server-used').selectMenu("setData", serverContent);
> +            $('#nfs-server-target').filterselect();
> +            $('input[name=nfsServerType]').change(function() {
> +                if ($(this).val() === 'input') {
> +                    $('#nfsServerInputDiv').removeClass('tmpl-html');
> +                    $('#nfsServerChooseDiv').addClass('tmpl-html');
> +                } else {
> +                    $('#nfsServerInputDiv').addClass('tmpl-html');
> +                    $('#nfsServerChooseDiv').removeClass('tmpl-html');
> +                }
> +            });
> +            $('#nfsServerSelect').change(function() {
> +                $('#nfsserverId').val($(this).val());
> +                $('#nfsserverId').trigger("keydown");
> +            });
> +            $('#nfsserverId').on("keydown",function() {
> +                if ($(this).val() !== '' && kimchi.isServer($(this).val())) {
> +                    $('#nfspathId').removeAttr('disabled');
> +                } else {
> +                    $('#nfspathId').attr('disabled','disabled');
> +                }
> +                $('#nfs-server-target').filterselect('clear');
> +            });
> +            $('#nfspathId').focus(function() {
> +                var targetContent = [];
> +                kimchi.getStorageTargets($('#nfsserverId').val(), 'netfs', function(data) {
> +                    if (data.length > 0) {
> +                        $.each(data, function(index, value) {
> +                            targetContent.push({
> +                                label : value.target,
> +                                value : value.target
> +                            });
> +                        });
> +                    }  else {
> +                        targetContent.push({
> +                            label : i18n['msg.no.result'],
> +                            value : ''
> +                        });
> +                    }
> +                    $('#nfs-server-target').filterselect("setData", targetContent);
> +                },function() {
> +                    targetContent.push({
> +                        label : i18n['msg.no.result'],
> +                        value : ''
> +                    });
> +                    $('#nfs-server-target').filterselect("setData", targetContent);
> +                });
> +            });
> +        });
>           $('#poolType').change(function() {
>               if ($(this).val() === 'dir') {
>                   $('.path-section').removeClass('tmpl-html');
> @@ -88,7 +153,6 @@ kimchi.validateForm = function() {
>       } else {
>           return kimchi.validateLogicalForm();
>       }
> -
>   };
>
>   kimchi.validateDirForm = function () {
> @@ -116,11 +180,8 @@ kimchi.validateNfsForm = function () {
>           kimchi.message.error(i18n['msg.pool.edit.nfspath.blank']);
>           return false;
>       }
> -    var domain = "([0-9a-z_!~*'()-]+\.)*([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\.[a-z]{2,6}"
> -    var ip = "(\\d{1,3}\.){3}\\d{1,3}"
> -    regex = new RegExp('^' + domain + '|' + ip + '$')
>
> -    if(!regex.test(nfsserver)) {
> +    if(kimchi.isServer(nfsserver)) {
>           kimchi.message.error(i18n['msg.validate.pool.edit.nfsserver']);
>           return false;
>       }
> diff --git a/ui/js/src/kimchi.utils.js b/ui/js/src/kimchi.utils.js
> index 8af6a11..6439db6 100644
> --- a/ui/js/src/kimchi.utils.js
> +++ b/ui/js/src/kimchi.utils.js
> @@ -163,3 +163,14 @@ kimchi.changetoProperUnit = function(numOrg, digits, base) {
>
>       kimchi.formatMeasurement = format;
>   })();
> +
> +kimchi.isServer = function(server) {
> +    var domain = "([0-9a-z_!~*'()-]+\.)*([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\.[a-z]{2,6}"
> +    var ip = "(\\d{1,3}\.){3}\\d{1,3}"
> +    regex = new RegExp('^' + domain + '|' + ip + '$');
> +    if(!regex.test(server)) {
> +        return false;
> +    } else {
> +        return true;
> +    }
> +};
> \ No newline at end of file
> diff --git a/ui/js/widgets/filter-select.js b/ui/js/widgets/filter-select.js
> new file mode 100644
> index 0000000..02fe5d9
> --- /dev/null
> +++ b/ui/js/widgets/filter-select.js
> @@ -0,0 +1,137 @@
> +/*
> + * Project Kimchi
> + *
> + * Copyright IBM, Corp. 2013
> + *
> + * Authors:
> + *  zhoumeina <zhoumein 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.
> + */
> +(function($) {
> +    $.widget('kimchi.filterselect', {
> +        options : {
> +            key : 0,
> +            value : 0
> +        },
> +
> +        _create : function() {
> +            this.listControl = this.element;
> +            this.element.html('');
> +            var targetId = this.listControl.data('target');
> +            var labelId = this.listControl.data('label');
> +            this.target = $('#' + targetId);
> +            this.label = $('#' + labelId);
> +            this.selectDiv = this.listControl.parent().parent();
> +            this.selectDiv.addClass('btn-select dropdown popable');
> +            this.listControl.parent().addClass('popover');
> +        },
> +
> +        _setOption : function(key, value) {
> +        },
> +
> +        setData : function(options) {
> +            this.element.html('');
> +            var that = this;
> +            var value = this.target.val();
> +            var selectedClass = 'active';
> +            var itemTag = 'li';
> +
> +            that.listControl.on('click', itemTag, function(e) {
> +                that.listControl.children().removeClass(selectedClass);
> +                that.listControl.addClass(selectedClass);
> +                that.target.text($(this).text());
> +                var oldValue = that.target.val();
> +                var newValue = $(this).data('value');
> +                that.target.val(newValue);
> +                if (oldValue !== newValue) {
> +                    that.target.change();
> +                }
> +            });
> +
> +            that.selectDiv.click(function(e) {
> +                that.listControl.html('');
> +                var items = that._dataList(options);
> +                $.each(items, function(index, item) {
> +                    that.listControl.append(item);
> +                })
> +                var isOpen = that.selectDiv.hasClass('open');
> +                that.selectDiv.removeClass('open');
> +                if (!isOpen && items.length > 0) {
> +                    that.selectDiv.addClass('open');
> +                }
> +                e.preventDefault();
> +                e.stopPropagation();
> +            });
> +
> +            that.target.keyup(function(event) {
> +                that.listControl.html('');
> +                var items = that._dataList(options);
> +                var temp = 0;
> +                $.each(items, function(index, item) {
> +                    if (item.html().indexOf(that.target.val()) >= 0) {
> +                        that.listControl.append(item);
> +                        temp++;
> +                    }
> +                });
> +                if (that.listControl.html() === '') {
> +                    that.listControl.html(i18n['msg.storagepool.unvalid.path']);
> +                }
> +                if (temp > 0) {
> +                    that._open();
> +                }
> +            });
> +        },
> +
> +        _dataList : function(options) {
> +            var item;
> +            var itemTag = 'li';
> +            var selectedClass = 'active';
> +            var items = [];
> +            var that = this;
> +            $.each(options, function(index, option) {
> +                item = $('<' + itemTag + '></' + itemTag + '>');
> +                item.text(option.label);
> +                item.data('value', option.value);
> +                if (option.value === that.target.val()) {
> +                    item.addClass(selectedClass);
> +                }
> +                items.push(item);
> +            });
> +            return items;
> +        },
> +
> +        clear : function() {
> +            this.target.val("");
> +        },
> +
> +        _open : function() {
> +            var isOpen = this.selectDiv.hasClass('open');
> +            if (!isOpen) {
> +                this.selectDiv.addClass('open');
> +            }
> +        },
> +
> +        _close : function() {
> +            var isOpen = this.selectDiv.hasClass('open');
> +            if (isOpen) {
> +                this.selectDiv.removeClass('open');
> +            }
> +        },
> +
> +        destroy : function() {
> +            // call the base destroy function
> +            $.Widget.prototype.destroy.call(this);
> +        }
> +    });
> +}(jQuery));
> \ No newline at end of file
> diff --git a/ui/js/widgets/select-menu.js b/ui/js/widgets/select-menu.js
> new file mode 100644
> index 0000000..4124096
> --- /dev/null
> +++ b/ui/js/widgets/select-menu.js
> @@ -0,0 +1,86 @@
> +/*
> + * Project Kimchi
> + *
> + * Copyright IBM, Corp. 2013
> + *
> + * Authors:
> + *  zhoumeina <zhoumein 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.
> + */
> +(function($) {
> +    $.widget('kimchi.selectMenu', {
> +        options: {
> +               key: 0,
> +               value: 0
> +              },
> +
> +        _create : function() {
> +            this.listControl = this.element;
> +            this.element.html('');
> +            var targetId = this.listControl.data('target');
> +            var labelId = this.listControl.data('label');
> +            this.target = $('#' + targetId);
> +            this.label = $('#' + labelId);
> +            this.selectDiv = this.listControl.parent().parent();
> +            this.selectDiv.addClass('btn-select dropdown popable');
> +            this.listControl.parent().addClass('popover');
> +            var that = this;
> +        },
> +
> +        _setOption : function(key,value) {},
> +
> +        setData : function (options) {
> +            var that = this;
> +            var value = this.target.val();
> +            var selectedClass = 'active';
> +            var itemTag = 'li';
> +            $.each(options, function(index, option) {
> +                item = $('<' + itemTag + '></' + itemTag + '>');
> +                item.text(option.label);
> +                item.data('value', option.value);
> +                if(option.value === value) {
> +                    item.addClass(selectedClass);
> +                    that.label.text(option.label);
> +                }
> +                that.listControl.append(item);
> +            });
> +            that.listControl.on('click', itemTag, function() {
> +                that.listControl.children().removeClass(selectedClass);
> +                that.listControl.addClass(selectedClass);
> +                that.label.text($(this).text());
> +                var oldValue = that.target.val();
> +                var newValue = $(this).data('value');
> +                that.target.val(newValue);
> +                if(oldValue !== newValue) {
> +                    that.target.change();
> +                }
> +            });
> +
> +            that.selectDiv.click(function(e) {
> +                var isOpen = that.selectDiv.hasClass('open');
> +                that.selectDiv.removeClass('open');
> +                if (!isOpen) {
> +                    that.selectDiv.addClass('open');
> +                }
> +                e.preventDefault();
> +                e.stopPropagation();
> +            });
> +        },
> +
> +        destroy : function() {
> +            // call the base destroy function
> +            $.Widget.prototype.destroy.call(this);
> +        }
> +    });
> +}(jQuery));
> \ No newline at end of file
> diff --git a/ui/pages/i18n.html.tmpl b/ui/pages/i18n.html.tmpl
> index c1fc3d1..c95da9b 100644
> --- a/ui/pages/i18n.html.tmpl
> +++ b/ui/pages/i18n.html.tmpl
> @@ -121,7 +121,10 @@ var i18n = {
>       'action_create': "$_("Create")",
>       'msg_warning': "$_("Warning")",
>       'msg.logicalpool.confirm.delete': "$_("It will format your disk and you will loose any data in"
> -                                      " there, are you sure to continue? ")"
> +                                      " there, are you sure to continue? ")",
> +    'select_default': "$_("Please choose")",
> +    'msg.storagepool.unvalid.path': "$_("This is not a valid path")",
> +    'msg.no.result' : "$_("No valid result")"
>   };
>   </script>
>   </body>
> diff --git a/ui/pages/storagepool-add.html.tmpl b/ui/pages/storagepool-add.html.tmpl
> index 5a2dd45..3a73390 100644
> --- a/ui/pages/storagepool-add.html.tmpl
> +++ b/ui/pages/storagepool-add.html.tmpl
> @@ -27,7 +27,7 @@
>   <!DOCTYPE html>
>   <html>
>   <body>
> -    <div class="window" style="width: 600px; height: 600px;">
> +    <div class="window" style="width: 800px; height: 650px;">
>           <header>
>               <h1 class="title">$_("Define a New Storage Pool")</h1>
>               <div class="close">X</div>
> @@ -47,10 +47,10 @@
>                   <section class="form-section">
>                       <h2>2. $_("Storage Pool Type")</h2>
>                       <div class="storage-type-wrapper-controls">
> -                        <div class="btn dropdown popable">
> +                        <div>
>                               <input id="poolType" name="type" type="hidden" value="dir"/>
>                               <span class="text" id="pool-type-label"></span><span class="arrow"></span>
> -                            <div class="popover" style="width: 100%">
> +                            <div style="width: 100%">
>                                   <ul class="select-list" id="storagePool-list" data-target="poolType" data-label="pool-type-label">
>                                   </ul>
>                               </div>
> @@ -74,22 +74,43 @@
>                       <section class="form-section">
>                           <h2>3. $_("NFS server IP")</h2>
>                           <div class="field">
> +                            <input type="radio" id="nfsServerInput" value="input" name="nfsServerType" checked="true">
> +                            <label>$_("I want to input the server myself.")</label>
> +                            <input type="radio" id="nfsServerChoose" value="choose" name="nfsServerType">
> +                            <label>$_("I want to choose a server I used before.")</label>
> +                        </div>
> +                        <div id="nfsServerInputDiv" class="field">
>                               <p class="text-help">
>                                   $_("NFS server IP or hostname. It should not be empty.")</p>
>                               <input id="nfsserverId" type="text" class="text"
>                                   style="width: 300px">
>                           </div>
> +                        <div id="nfsServerChooseDiv" class="field tmpl-html" style="overflow:visible">
> +                            <p class="text-help">
> +                                $_("Please choose the nfs server you want to create storage pool.")</p>
> +                            <div style="width: 285px">
> +                                <input id="nfsServerSelect" type="hidden"/>
> +                                <span class="text" id="nfs-server-label"></span><span class="arrow"></span>
> +                                <div style="width: 100%">
> +                                    <ul class="select-list" id="nfs-server-used" data-target="nfsServerSelect" data-label="nfs-server-label">
> +                                    </ul>
> +                                </div>
> +                            </div>
> +                        </div>
>                       </section>
>                       <section class="form-section">
>                           <h2>4. $_("NFS Path")</h2>
> -                        <div class="field">
> +                        <div class="field" style="overflow:visible">
>                               <p class="text-help">$_("The nfs exported path on nfs server")</p>
> -                            <input id="nfspathId" type="text" class="text"
> -                                style="width: 300px">
> -                            <input type="hidden" id="localpathId" class="text"
> -                                value="none">
> -                        </div>
> -                        <div class="clear"></div>
> +		                    <div style="width: 285px">
> +		                        <input id="nfspathId" class="text" disabled="disabled" style="width:293px;"/>
> +		                        <span class="text" id="nfs-target-label"></span><span class="arrow"></span>
> +		                        <div style="width: 100%">
> +		                            <ul class="select-list" id="nfs-server-target" data-target="nfspathId" data-label="nfs-target-label">
> +		                            </ul>
> +		                        </div>
> +		                    </div>
> +	                    </div>
>                       </section>
>                   </div>
>                   <div class="logical-section tmpl-html">




More information about the Kimchi-devel mailing list