[Kimchi-devel] [PATCH V2] Add nfs server and target UI in create storage pool
zhoumeina
zhoumein at linux.vnet.ibm.com
Mon Dec 30 09:42:28 UTC 2013
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">
--
1.7.1
More information about the Kimchi-devel
mailing list