
From: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> Signed-off-by: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> --- ui/css/theme-default/list.css | 18 ++++++++++ ui/js/src/kimchi.api.js | 17 ++++++++- ui/js/src/kimchi.guest_main.js | 73 +++++++++++++++++++++++++++++++++++---- ui/pages/guest.html.tmpl | 6 +++- 4 files changed, 103 insertions(+), 11 deletions(-) diff --git a/ui/css/theme-default/list.css b/ui/css/theme-default/list.css index 8ffee69..fc3017b 100644 --- a/ui/css/theme-default/list.css +++ b/ui/css/theme-default/list.css @@ -275,3 +275,21 @@ text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff; padding-left: 10px; } + +.guest-clone { + margin: 10px; +} + +.guest-clone .icon { + background: url('../../images/theme-default/loading.gif') no-repeat; + display: inline-block; + width: 20px; + height: 20px; + vertical-align: middle; +} + +.guest-clone .text { + color: #666666; + margin-left: 5px; + text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF; +} diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js index 5895a07..2f90219 100644 --- a/ui/js/src/kimchi.api.js +++ b/ui/js/src/kimchi.api.js @@ -695,10 +695,10 @@ var kimchi = { }, 2000); break; case 'finished': - suc(result); + suc && suc(result); break; case 'failed': - err(result); + err && err(result); break; default: break; @@ -1233,5 +1233,18 @@ var kimchi = { success : suc, error : err }); + }, + + cloneGuest: function(vm, suc, err) { + kimchi.requestJSON({ + url : kimchi.url + 'vms/'+encodeURIComponent(vm)+"/clone", + type : 'POST', + contentType : 'application/json', + dataType : 'json', + success : suc, + error : err ? err : function(data) { + kimchi.message.error(data.responseJSON.reason); + } + }); } }; diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js index dbe8753..ecc3b7a 100644 --- a/ui/js/src/kimchi.guest_main.js +++ b/ui/js/src/kimchi.guest_main.js @@ -15,6 +15,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +kimchi.sampleGuestObject = { + "name": "", + "uuid": "", + "state": "shutoff", + "persistent": true, + "icon": null, + "cpus": 0, + "memory": 0, + "stats": { + "net_throughput": 0, + "io_throughput_peak": 100, + "cpu_utilization": 0, + "io_throughput": 0, + "net_throughput_peak": 100 + }, + "screenshot": null, + "graphics": { + "passwd": null, + "passwdValidTo": null, + "type": "vnc", + "port": null, + "listen": "127.0.0.1" + }, + "users": [], + "groups": [], + "access": "full" +}; + kimchi.vmstart = function(event) { var button=$(this); @@ -173,8 +201,24 @@ kimchi.listVmsAuto = function() { if (kimchi.vmTimeout) { clearTimeout(kimchi.vmTimeout); } + var getCloningGuests = function(){ + var guests = []; + kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/*'), function(tasks) { + for(var i=0;i<tasks.length;i++){ + var guestUri = tasks[i].target_uri; + var guestName = guestUri.substring(guestUri.lastIndexOf('/')+1, guestUri.length); + guests.push($.extend({}, kimchi.sampleGuestObject, {name: guestName, isCloning: true})); + if(kimchi.trackingTasks.indexOf(tasks[i].id)==-1) + kimchi.trackTask(tasks[i].id, null, function(err){ + kimchi.message.error(err.message); + }, null); + } + }, null, true); + return guests; + }; kimchi.listVMs(function(result, textStatus, jqXHR) { if (result && textStatus=="success") { + result = getCloningGuests().concat(result); if(result.length) { var listHtml = ''; var guestTemplate = kimchi.guestTemplate; @@ -233,14 +277,16 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { imgLoad.attr('src',load_src); //Link the stopped tile to the start action, the running tile to open the console - if (vmRunningBool) { - liveTile.off("click", kimchi.vmstart); - liveTile.on("click", kimchi.openVmConsole); - } - else { - liveTile.off("click", kimchi.openVmConsole); - liveTile.on("click", kimchi.vmstart); - liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); + if(!vmObject.isCloning){ + if (vmRunningBool) { + liveTile.off("click", kimchi.vmstart); + liveTile.on("click", kimchi.openVmConsole); + } + else { + liveTile.off("click", kimchi.openVmConsole); + liveTile.on("click", kimchi.vmstart); + liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); + } } @@ -257,6 +303,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { //Setup the VM Actions var guestActions=result.find("div[name=guest-actions]"); guestActions.find(".shutoff-disabled").prop('disabled', !vmRunningBool ); + guestActions.find(".running-disabled").prop('disabled', vmRunningBool ); if (vmRunningBool) { guestActions.find(".running-hidden").hide(); @@ -286,6 +333,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { } guestActions.find("[name=vm-edit]").on({click : kimchi.vmedit}); guestActions.find("[name=vm-delete]").on({click : kimchi.vmdelete}); + guestActions.find("[name=vm-clone]").click(function(){ + kimchi.cloneGuest($(this).closest('li[name=guest]').attr("id"), function(data){ + kimchi.listVmsAuto(); + }); + }); //Maintain menu open state var actionMenu=guestActions.find("div[name=actionmenu]"); @@ -293,6 +345,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { $('.popover', actionMenu).toggle(); } + if(vmObject.isCloning){ + guestActions.children().hide(); + result.find('.guest-clone').removeClass('hide-content'); + } + return result; }; diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl index 43fb350..74206fd 100644 --- a/ui/pages/guest.html.tmpl +++ b/ui/pages/guest.html.tmpl @@ -26,6 +26,9 @@ <div class="guest-general"> <h2 class="title" title="{name}">{name}</h2> </div> + <div class="guest-clone hide-content"> + <span class="icon"></span><span class="text">$_("Cloning")...</span> + </div> </div> <div name="cpu_utilization" class="sortable"> <div class="circleGauge"></div> @@ -56,7 +59,8 @@ <span class="text">$_("Actions")</span><span class="arrow"></span> <div class="popover actionsheet right-side" style="width: 250px"> <button class="button-big shutoff-disabled" name="vm-console" ><span class="text">$_("Connect")</span></button> - <button class="button-big running-disabled" name="vm-edit"><span class="text">$_("Edit")</span></button> + <button class="button-big running-disabled" name="vm-clone"><span class="text">$_("Clone")</span></button> + <button class="button-big" name="vm-edit"><span class="text">$_("Edit")</span></button> <button class="button-big shutoff-hidden" name="vm-reset"><span class="text">$_("Reset")</span></button> <button class="button-big shutoff-hidden" name="vm-shutdown"><span class="text">$_("Shut Down")</span></button> <button class="button-big running-hidden" name="vm-start"><span class="text">$_("Start")</span></button> -- 1.7.1

I'd like to do two suggestions: 1) Add a progress bar just below the 'Cloning..." text to show the cloning progress - libvirt provides this info; and 2) Add a warning screen when user select to clone a VM informing that the new virtual disk will be located in the default storage pool if the VM is using a iSCSI storage pool and/or the storage pool of the VM to be cloned doesn't have enough space in disk. It's a informative warning that must be displayed every time that a VM is choosed to be cloned (like when user select to execute a Power Off of some running guest). I know that the second topic was discussed in previous scrum meetings, but it's important alert the user about the backend operations. Thanks and best regards, -- Paulo Ricardo Paz Vital <pvital@linux.vnet.ibm.com> IBM Linux Technology Center
From: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com>
Signed-off-by: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> --- ui/css/theme-default/list.css | 18 ++++++++++ ui/js/src/kimchi.api.js | 17 ++++++++- ui/js/src/kimchi.guest_main.js | 73 +++++++++++++++++++++++++++++++++++---- ui/pages/guest.html.tmpl | 6 +++- 4 files changed, 103 insertions(+), 11 deletions(-)
diff --git a/ui/css/theme-default/list.css b/ui/css/theme-default/list.css index 8ffee69..fc3017b 100644 --- a/ui/css/theme-default/list.css +++ b/ui/css/theme-default/list.css @@ -275,3 +275,21 @@ text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff; padding-left: 10px; } + +.guest-clone { + margin: 10px; +} + +.guest-clone .icon { + background: url('../../images/theme-default/loading.gif') no-repeat; + display: inline-block; + width: 20px; + height: 20px; + vertical-align: middle; +} + +.guest-clone .text { + color: #666666; + margin-left: 5px; + text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF; +} diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js index 5895a07..2f90219 100644 --- a/ui/js/src/kimchi.api.js +++ b/ui/js/src/kimchi.api.js @@ -695,10 +695,10 @@ var kimchi = { }, 2000); break; case 'finished': - suc(result); + suc && suc(result); break; case 'failed': - err(result); + err && err(result); break; default: break; @@ -1233,5 +1233,18 @@ var kimchi = { success : suc, error : err }); + }, + + cloneGuest: function(vm, suc, err) { + kimchi.requestJSON({ + url : kimchi.url + 'vms/'+encodeURIComponent(vm)+"/clone", + type : 'POST', + contentType : 'application/json', + dataType : 'json', + success : suc, + error : err ? err : function(data) { + kimchi.message.error(data.responseJSON.reason); + } + }); } }; diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js index dbe8753..ecc3b7a 100644 --- a/ui/js/src/kimchi.guest_main.js +++ b/ui/js/src/kimchi.guest_main.js @@ -15,6 +15,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +kimchi.sampleGuestObject = { + "name": "", + "uuid": "", + "state": "shutoff", + "persistent": true, + "icon": null, + "cpus": 0, + "memory": 0, + "stats": { + "net_throughput": 0, + "io_throughput_peak": 100, + "cpu_utilization": 0, + "io_throughput": 0, + "net_throughput_peak": 100 + }, + "screenshot": null, + "graphics": { + "passwd": null, + "passwdValidTo": null, + "type": "vnc", + "port": null, + "listen": "127.0.0.1" + }, + "users": [], + "groups": [], + "access": "full" +}; +
kimchi.vmstart = function(event) { var button=$(this); @@ -173,8 +201,24 @@ kimchi.listVmsAuto = function() { if (kimchi.vmTimeout) { clearTimeout(kimchi.vmTimeout); } + var getCloningGuests = function(){ + var guests = []; + kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/*'), function(tasks) { + for(var i=0;i<tasks.length;i++){ + var guestUri = tasks[i].target_uri; + var guestName = guestUri.substring(guestUri.lastIndexOf('/')+1, guestUri.length); + guests.push($.extend({}, kimchi.sampleGuestObject, {name: guestName, isCloning: true})); + if(kimchi.trackingTasks.indexOf(tasks[i].id)==-1) + kimchi.trackTask(tasks[i].id, null, function(err){ + kimchi.message.error(err.message); + }, null); + } + }, null, true); + return guests; + }; kimchi.listVMs(function(result, textStatus, jqXHR) { if (result && textStatus=="success") { + result = getCloningGuests().concat(result); if(result.length) { var listHtml = ''; var guestTemplate = kimchi.guestTemplate; @@ -233,14 +277,16 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { imgLoad.attr('src',load_src);
//Link the stopped tile to the start action, the running tile to open the console - if (vmRunningBool) { - liveTile.off("click", kimchi.vmstart); - liveTile.on("click", kimchi.openVmConsole); - } - else { - liveTile.off("click", kimchi.openVmConsole); - liveTile.on("click", kimchi.vmstart); - liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); + if(!vmObject.isCloning){ + if (vmRunningBool) { + liveTile.off("click", kimchi.vmstart); + liveTile.on("click", kimchi.openVmConsole); + } + else { + liveTile.off("click", kimchi.openVmConsole); + liveTile.on("click", kimchi.vmstart); + liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); + } }
@@ -257,6 +303,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { //Setup the VM Actions var guestActions=result.find("div[name=guest-actions]"); guestActions.find(".shutoff-disabled").prop('disabled', !vmRunningBool ); + guestActions.find(".running-disabled").prop('disabled', vmRunningBool );
if (vmRunningBool) { guestActions.find(".running-hidden").hide(); @@ -286,6 +333,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { } guestActions.find("[name=vm-edit]").on({click : kimchi.vmedit}); guestActions.find("[name=vm-delete]").on({click : kimchi.vmdelete}); + guestActions.find("[name=vm-clone]").click(function(){ + kimchi.cloneGuest($(this).closest('li[name=guest]').attr("id"), function(data){ + kimchi.listVmsAuto(); + }); + });
//Maintain menu open state var actionMenu=guestActions.find("div[name=actionmenu]"); @@ -293,6 +345,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { $('.popover', actionMenu).toggle(); }
+ if(vmObject.isCloning){ + guestActions.children().hide(); + result.find('.guest-clone').removeClass('hide-content'); + } + return result; };
diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl index 43fb350..74206fd 100644 --- a/ui/pages/guest.html.tmpl +++ b/ui/pages/guest.html.tmpl @@ -26,6 +26,9 @@ <div class="guest-general"> <h2 class="title" title="{name}">{name}</h2> </div> + <div class="guest-clone hide-content"> + <span class="icon"></span><span class="text">$_("Cloning")...</span> + </div> </div> <div name="cpu_utilization" class="sortable"> <div class="circleGauge"></div> @@ -56,7 +59,8 @@ <span class="text">$_("Actions")</span><span class="arrow"></span> <div class="popover actionsheet right-side" style="width: 250px"> <button class="button-big shutoff-disabled" name="vm-console" ><span class="text">$_("Connect")</span></button> - <button class="button-big running-disabled" name="vm-edit"><span class="text">$_("Edit")</span></button> + <button class="button-big running-disabled" name="vm-clone"><span class="text">$_("Clone")</span></button> + <button class="button-big" name="vm-edit"><span class="text">$_("Edit")</span></button> <button class="button-big shutoff-hidden" name="vm-reset"><span class="text">$_("Reset")</span></button> <button class="button-big shutoff-hidden" name="vm-shutdown"><span class="text">$_("Shut Down")</span></button> <button class="button-big running-hidden" name="vm-start"><span class="text">$_("Start")</span></button>

On 10/28/2014 3:18 AM, Paulo Ricardo Paz Vital wrote: > I'd like to do two suggestions: > 1) Add a progress bar just below the 'Cloning..." text to show the > cloning progress - libvirt provides this info; and I assume the progress is overall cloning progress. It is helpful, let me know where to get it from API response. > 2) Add a warning screen when user select to clone a VM informing that > the new virtual disk will be located in the default storage pool if the > VM is using a iSCSI storage pool and/or the storage pool of the VM to be > cloned doesn't have enough space in disk. It's a informative warning > that must be displayed every time that a VM is choosed to be cloned > (like when user select to execute a Power Off of some running guest). If it is just a static alert message, I think it is better to put it into help doc. 1. This alert message is only valuable when user first time clone a guest, once he/she got to know that, it is completely redundant for daily operations. 2. Clone is not an operation like 'Delete' that will lead to harmful, unrecoverable result. If user dislike it after cloning, he can delete the cloned guest. 3. The default clone is to pursue truly desired outcome for user to have a new guest with rid of any noisy distraction. As we have discussed, default clone is not functional enough and a 'Custom Clone' will be added. in 'Custom Clone' A dialog will popup with all customization options for user to select, including select the target pool for each virtual disk of the guest. > > I know that the second topic was discussed in previous scrum meetings, > but it's important alert the user about the backend operations. > > Thanks and best regards, > -- Paulo Ricardo Paz Vital <pvital@linux.vnet.ibm.com> IBM Linux > Technology Center >> >From: Yu Xin Huo<huoyuxin@linux.vnet.ibm.com> >> > >> >Signed-off-by: Yu Xin Huo<huoyuxin@linux.vnet.ibm.com> >> >--- >> > ui/css/theme-default/list.css | 18 ++++++++++ >> > ui/js/src/kimchi.api.js | 17 ++++++++- >> > ui/js/src/kimchi.guest_main.js | 73 +++++++++++++++++++++++++++++++++++---- >> > ui/pages/guest.html.tmpl | 6 +++- >> > 4 files changed, 103 insertions(+), 11 deletions(-) >> > >> >diff --git a/ui/css/theme-default/list.css b/ui/css/theme-default/list.css >> >index 8ffee69..fc3017b 100644 >> >--- a/ui/css/theme-default/list.css >> >+++ b/ui/css/theme-default/list.css >> >@@ -275,3 +275,21 @@ >> > text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff; >> > padding-left: 10px; >> > } >> >+ >> >+.guest-clone { >> >+ margin: 10px; >> >+} >> >+ >> >+.guest-clone .icon { >> >+ background: url('../../images/theme-default/loading.gif') no-repeat; >> >+ display: inline-block; >> >+ width: 20px; >> >+ height: 20px; >> >+ vertical-align: middle; >> >+} >> >+ >> >+.guest-clone .text { >> >+ color: #666666; >> >+ margin-left: 5px; >> >+ text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF; >> >+} >> >diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js >> >index 5895a07..2f90219 100644 >> >--- a/ui/js/src/kimchi.api.js >> >+++ b/ui/js/src/kimchi.api.js >> >@@ -695,10 +695,10 @@ var kimchi = { >> > }, 2000); >> > break; >> > case 'finished': >> >- suc(result); >> >+ suc && suc(result); >> > break; >> > case 'failed': >> >- err(result); >> >+ err && err(result); >> > break; >> > default: >> > break; >> >@@ -1233,5 +1233,18 @@ var kimchi = { >> > success : suc, >> > error : err >> > }); >> >+ }, >> >+ >> >+ cloneGuest: function(vm, suc, err) { >> >+ kimchi.requestJSON({ >> >+ url : kimchi.url + 'vms/'+encodeURIComponent(vm)+"/clone", >> >+ type : 'POST', >> >+ contentType : 'application/json', >> >+ dataType : 'json', >> >+ success : suc, >> >+ error : err ? err : function(data) { >> >+ kimchi.message.error(data.responseJSON.reason); >> >+ } >> >+ }); >> > } >> > }; >> >diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js >> >index dbe8753..ecc3b7a 100644 >> >--- a/ui/js/src/kimchi.guest_main.js >> >+++ b/ui/js/src/kimchi.guest_main.js >> >@@ -15,6 +15,34 @@ >> > * See the License for the specific language governing permissions and >> > * limitations under the License. >> > */ >> >+kimchi.sampleGuestObject = { >> >+ "name": "", >> >+ "uuid": "", >> >+ "state": "shutoff", >> >+ "persistent": true, >> >+ "icon": null, >> >+ "cpus": 0, >> >+ "memory": 0, >> >+ "stats": { >> >+ "net_throughput": 0, >> >+ "io_throughput_peak": 100, >> >+ "cpu_utilization": 0, >> >+ "io_throughput": 0, >> >+ "net_throughput_peak": 100 >> >+ }, >> >+ "screenshot": null, >> >+ "graphics": { >> >+ "passwd": null, >> >+ "passwdValidTo": null, >> >+ "type": "vnc", >> >+ "port": null, >> >+ "listen": "127.0.0.1" >> >+ }, >> >+ "users": [], >> >+ "groups": [], >> >+ "access": "full" >> >+}; >> >+ >> > >> > kimchi.vmstart = function(event) { >> > var button=$(this); >> >@@ -173,8 +201,24 @@ kimchi.listVmsAuto = function() { >> > if (kimchi.vmTimeout) { >> > clearTimeout(kimchi.vmTimeout); >> > } >> >+ var getCloningGuests = function(){ >> >+ var guests = []; >> >+ kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/*'), function(tasks) { >> >+ for(var i=0;i<tasks.length;i++){ >> >+ var guestUri = tasks[i].target_uri; >> >+ var guestName = guestUri.substring(guestUri.lastIndexOf('/')+1, guestUri.length); >> >+ guests.push($.extend({}, kimchi.sampleGuestObject, {name: guestName, isCloning: true})); >> >+ if(kimchi.trackingTasks.indexOf(tasks[i].id)==-1) >> >+ kimchi.trackTask(tasks[i].id, null, function(err){ >> >+ kimchi.message.error(err.message); >> >+ }, null); >> >+ } >> >+ }, null, true); >> >+ return guests; >> >+ }; >> > kimchi.listVMs(function(result, textStatus, jqXHR) { >> > if (result && textStatus=="success") { >> >+ result = getCloningGuests().concat(result); >> > if(result.length) { >> > var listHtml = ''; >> > var guestTemplate = kimchi.guestTemplate; >> >@@ -233,14 +277,16 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { >> > imgLoad.attr('src',load_src); >> > >> > //Link the stopped tile to the start action, the running tile to open the console >> >- if (vmRunningBool) { >> >- liveTile.off("click", kimchi.vmstart); >> >- liveTile.on("click", kimchi.openVmConsole); >> >- } >> >- else { >> >- liveTile.off("click", kimchi.openVmConsole); >> >- liveTile.on("click", kimchi.vmstart); >> >- liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); >> >+ if(!vmObject.isCloning){ >> >+ if (vmRunningBool) { >> >+ liveTile.off("click", kimchi.vmstart); >> >+ liveTile.on("click", kimchi.openVmConsole); >> >+ } >> >+ else { >> >+ liveTile.off("click", kimchi.openVmConsole); >> >+ liveTile.on("click", kimchi.vmstart); >> >+ liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); >> >+ } >> > } >> > >> > >> >@@ -257,6 +303,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { >> > //Setup the VM Actions >> > var guestActions=result.find("div[name=guest-actions]"); >> > guestActions.find(".shutoff-disabled").prop('disabled', !vmRunningBool ); >> >+ guestActions.find(".running-disabled").prop('disabled', vmRunningBool ); >> > >> > if (vmRunningBool) { >> > guestActions.find(".running-hidden").hide(); >> >@@ -286,6 +333,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { >> > } >> > guestActions.find("[name=vm-edit]").on({click : kimchi.vmedit}); >> > guestActions.find("[name=vm-delete]").on({click : kimchi.vmdelete}); >> >+ guestActions.find("[name=vm-clone]").click(function(){ >> >+ kimchi.cloneGuest($(this).closest('li[name=guest]').attr("id"), function(data){ >> >+ kimchi.listVmsAuto(); >> >+ }); >> >+ }); >> > >> > //Maintain menu open state >> > var actionMenu=guestActions.find("div[name=actionmenu]"); >> >@@ -293,6 +345,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { >> > $('.popover', actionMenu).toggle(); >> > } >> > >> >+ if(vmObject.isCloning){ >> >+ guestActions.children().hide(); >> >+ result.find('.guest-clone').removeClass('hide-content'); >> >+ } >> >+ >> > return result; >> > }; >> > >> >diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl >> >index 43fb350..74206fd 100644 >> >--- a/ui/pages/guest.html.tmpl >> >+++ b/ui/pages/guest.html.tmpl >> >@@ -26,6 +26,9 @@ >> > <div class="guest-general"> >> > <h2 class="title" title="{name}">{name}</h2> >> > </div> >> >+ <div class="guest-clone hide-content"> >> >+ <span class="icon"></span><span class="text">$_("Cloning")...</span> >> >+ </div> >> > </div> >> > <div name="cpu_utilization" class="sortable"> >> > <div class="circleGauge"></div> >> >@@ -56,7 +59,8 @@ >> > <span class="text">$_("Actions")</span><span class="arrow"></span> >> > <div class="popover actionsheet right-side" style="width: 250px"> >> > <button class="button-big shutoff-disabled" name="vm-console" ><span class="text">$_("Connect")</span></button> >> >- <button class="button-big running-disabled" name="vm-edit"><span class="text">$_("Edit")</span></button> >> >+ <button class="button-big running-disabled" name="vm-clone"><span class="text">$_("Clone")</span></button> >> >+ <button class="button-big" name="vm-edit"><span class="text">$_("Edit")</span></button> >> > <button class="button-big shutoff-hidden" name="vm-reset"><span class="text">$_("Reset")</span></button> >> > <button class="button-big shutoff-hidden" name="vm-shutdown"><span class="text">$_("Shut Down")</span></button> >> > <button class="button-big running-hidden" name="vm-start"><span class="text">$_("Start")</span></button>

On 10/28/2014 06:58 AM, Yu Xin Huo wrote: > On 10/28/2014 3:18 AM, Paulo Ricardo Paz Vital wrote: >> I'd like to do two suggestions: >> 1) Add a progress bar just below the 'Cloning..." text to show the >> cloning progress - libvirt provides this info; and > I assume the progress is overall cloning progress. It is helpful, let > me know where to get it from API response. I think it will come on Task message parameter in a same way we did for upload/download. But I will let Cristian confirm it. >> 2) Add a warning screen when user select to clone a VM informing that >> the new virtual disk will be located in the default storage pool if the >> VM is using a iSCSI storage pool and/or the storage pool of the VM to be >> cloned doesn't have enough space in disk. It's a informative warning >> that must be displayed every time that a VM is choosed to be cloned >> (like when user select to execute a Power Off of some running guest). > If it is just a static alert message, I think it is better to put it > into help doc. > 1. This alert message is only valuable when user first time clone a > guest, once he/she got to know that, it is completely redundant for > daily operations. > 2. Clone is not an operation like 'Delete' that will lead to harmful, > unrecoverable result. If user dislike it after cloning, he can delete > the cloned guest. > 3. The default clone is to pursue truly desired outcome for user to > have a new guest with rid of any noisy distraction. > > As we have discussed, default clone is not functional enough and a > 'Custom Clone' will be added. in 'Custom Clone' > A dialog will popup with all customization options for user to select, > including select the target pool for each virtual disk of the guest. I was talking to Leonardo and Paulo offline and they came up an important point: Kimchi must be easy to use - which also means the user should not need to read any documentation to get it working as desired. And as Paulo mentioned/proposed it will be an static warning, so we don't need to have a clone pre-check or so. It would work in the same way when we power off the guest. Based on all that, I also agree with this warning message. Something like: "When the target guest has SCSI or iSCSI volumes, they will be cloned on default storage pool. The same will happen when the target pool does not have enough space to clone the volumes. Do you want to continue?" >> I know that the second topic was discussed in previous scrum meetings, >> but it's important alert the user about the backend operations. >> >> Thanks and best regards, >> -- Paulo Ricardo Paz Vital <pvital@linux.vnet.ibm.com> IBM Linux >> Technology Center >>> >From: Yu Xin Huo<huoyuxin@linux.vnet.ibm.com> >>> > >>> >Signed-off-by: Yu Xin Huo<huoyuxin@linux.vnet.ibm.com> >>> >--- >>> > ui/css/theme-default/list.css | 18 ++++++++++ >>> > ui/js/src/kimchi.api.js | 17 ++++++++- >>> > ui/js/src/kimchi.guest_main.js | 73 +++++++++++++++++++++++++++++++++++---- >>> > ui/pages/guest.html.tmpl | 6 +++- >>> > 4 files changed, 103 insertions(+), 11 deletions(-) >>> > >>> >diff --git a/ui/css/theme-default/list.css b/ui/css/theme-default/list.css >>> >index 8ffee69..fc3017b 100644 >>> >--- a/ui/css/theme-default/list.css >>> >+++ b/ui/css/theme-default/list.css >>> >@@ -275,3 +275,21 @@ >>> > text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff; >>> > padding-left: 10px; >>> > } >>> >+ >>> >+.guest-clone { >>> >+ margin: 10px; >>> >+} >>> >+ >>> >+.guest-clone .icon { >>> >+ background: url('../../images/theme-default/loading.gif') no-repeat; >>> >+ display: inline-block; >>> >+ width: 20px; >>> >+ height: 20px; >>> >+ vertical-align: middle; >>> >+} >>> >+ >>> >+.guest-clone .text { >>> >+ color: #666666; >>> >+ margin-left: 5px; >>> >+ text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF; >>> >+} >>> >diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js >>> >index 5895a07..2f90219 100644 >>> >--- a/ui/js/src/kimchi.api.js >>> >+++ b/ui/js/src/kimchi.api.js >>> >@@ -695,10 +695,10 @@ var kimchi = { >>> > }, 2000); >>> > break; >>> > case 'finished': >>> >- suc(result); >>> >+ suc && suc(result); >>> > break; >>> > case 'failed': >>> >- err(result); >>> >+ err && err(result); >>> > break; >>> > default: >>> > break; >>> >@@ -1233,5 +1233,18 @@ var kimchi = { >>> > success : suc, >>> > error : err >>> > }); >>> >+ }, >>> >+ >>> >+ cloneGuest: function(vm, suc, err) { >>> >+ kimchi.requestJSON({ >>> >+ url : kimchi.url + 'vms/'+encodeURIComponent(vm)+"/clone", >>> >+ type : 'POST', >>> >+ contentType : 'application/json', >>> >+ dataType : 'json', >>> >+ success : suc, >>> >+ error : err ? err : function(data) { >>> >+ kimchi.message.error(data.responseJSON.reason); >>> >+ } >>> >+ }); >>> > } >>> > }; >>> >diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js >>> >index dbe8753..ecc3b7a 100644 >>> >--- a/ui/js/src/kimchi.guest_main.js >>> >+++ b/ui/js/src/kimchi.guest_main.js >>> >@@ -15,6 +15,34 @@ >>> > * See the License for the specific language governing permissions and >>> > * limitations under the License. >>> > */ >>> >+kimchi.sampleGuestObject = { >>> >+ "name": "", >>> >+ "uuid": "", >>> >+ "state": "shutoff", >>> >+ "persistent": true, >>> >+ "icon": null, >>> >+ "cpus": 0, >>> >+ "memory": 0, >>> >+ "stats": { >>> >+ "net_throughput": 0, >>> >+ "io_throughput_peak": 100, >>> >+ "cpu_utilization": 0, >>> >+ "io_throughput": 0, >>> >+ "net_throughput_peak": 100 >>> >+ }, >>> >+ "screenshot": null, >>> >+ "graphics": { >>> >+ "passwd": null, >>> >+ "passwdValidTo": null, >>> >+ "type": "vnc", >>> >+ "port": null, >>> >+ "listen": "127.0.0.1" >>> >+ }, >>> >+ "users": [], >>> >+ "groups": [], >>> >+ "access": "full" >>> >+}; >>> >+ >>> > >>> > kimchi.vmstart = function(event) { >>> > var button=$(this); >>> >@@ -173,8 +201,24 @@ kimchi.listVmsAuto = function() { >>> > if (kimchi.vmTimeout) { >>> > clearTimeout(kimchi.vmTimeout); >>> > } >>> >+ var getCloningGuests = function(){ >>> >+ var guests = []; >>> >+ kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/*'), function(tasks) { >>> >+ for(var i=0;i<tasks.length;i++){ >>> >+ var guestUri = tasks[i].target_uri; >>> >+ var guestName = guestUri.substring(guestUri.lastIndexOf('/')+1, guestUri.length); >>> >+ guests.push($.extend({}, kimchi.sampleGuestObject, {name: guestName, isCloning: true})); >>> >+ if(kimchi.trackingTasks.indexOf(tasks[i].id)==-1) >>> >+ kimchi.trackTask(tasks[i].id, null, function(err){ >>> >+ kimchi.message.error(err.message); >>> >+ }, null); >>> >+ } >>> >+ }, null, true); >>> >+ return guests; >>> >+ }; >>> > kimchi.listVMs(function(result, textStatus, jqXHR) { >>> > if (result && textStatus=="success") { >>> >+ result = getCloningGuests().concat(result); >>> > if(result.length) { >>> > var listHtml = ''; >>> > var guestTemplate = kimchi.guestTemplate; >>> >@@ -233,14 +277,16 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { >>> > imgLoad.attr('src',load_src); >>> > >>> > //Link the stopped tile to the start action, the running tile to open the console >>> >- if (vmRunningBool) { >>> >- liveTile.off("click", kimchi.vmstart); >>> >- liveTile.on("click", kimchi.openVmConsole); >>> >- } >>> >- else { >>> >- liveTile.off("click", kimchi.openVmConsole); >>> >- liveTile.on("click", kimchi.vmstart); >>> >- liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); >>> >+ if(!vmObject.isCloning){ >>> >+ if (vmRunningBool) { >>> >+ liveTile.off("click", kimchi.vmstart); >>> >+ liveTile.on("click", kimchi.openVmConsole); >>> >+ } >>> >+ else { >>> >+ liveTile.off("click", kimchi.openVmConsole); >>> >+ liveTile.on("click", kimchi.vmstart); >>> >+ liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); >>> >+ } >>> > } >>> > >>> > >>> >@@ -257,6 +303,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { >>> > //Setup the VM Actions >>> > var guestActions=result.find("div[name=guest-actions]"); >>> > guestActions.find(".shutoff-disabled").prop('disabled', !vmRunningBool ); >>> >+ guestActions.find(".running-disabled").prop('disabled', vmRunningBool ); >>> > >>> > if (vmRunningBool) { >>> > guestActions.find(".running-hidden").hide(); >>> >@@ -286,6 +333,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { >>> > } >>> > guestActions.find("[name=vm-edit]").on({click : kimchi.vmedit}); >>> > guestActions.find("[name=vm-delete]").on({click : kimchi.vmdelete}); >>> >+ guestActions.find("[name=vm-clone]").click(function(){ >>> >+ kimchi.cloneGuest($(this).closest('li[name=guest]').attr("id"), function(data){ >>> >+ kimchi.listVmsAuto(); >>> >+ }); >>> >+ }); >>> > >>> > //Maintain menu open state >>> > var actionMenu=guestActions.find("div[name=actionmenu]"); >>> >@@ -293,6 +345,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { >>> > $('.popover', actionMenu).toggle(); >>> > } >>> > >>> >+ if(vmObject.isCloning){ >>> >+ guestActions.children().hide(); >>> >+ result.find('.guest-clone').removeClass('hide-content'); >>> >+ } >>> >+ >>> > return result; >>> > }; >>> > >>> >diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl >>> >index 43fb350..74206fd 100644 >>> >--- a/ui/pages/guest.html.tmpl >>> >+++ b/ui/pages/guest.html.tmpl >>> >@@ -26,6 +26,9 @@ >>> > <div class="guest-general"> >>> > <h2 class="title" title="{name}">{name}</h2> >>> > </div> >>> >+ <div class="guest-clone hide-content"> >>> >+ <span class="icon"></span><span class="text">$_("Cloning")...</span> >>> >+ </div> >>> > </div> >>> > <div name="cpu_utilization" class="sortable"> >>> > <div class="circleGauge"></div> >>> >@@ -56,7 +59,8 @@ >>> > <span class="text">$_("Actions")</span><span class="arrow"></span> >>> > <div class="popover actionsheet right-side" style="width: 250px"> >>> > <button class="button-big shutoff-disabled" name="vm-console" ><span class="text">$_("Connect")</span></button> >>> >- <button class="button-big running-disabled" name="vm-edit"><span class="text">$_("Edit")</span></button> >>> >+ <button class="button-big running-disabled" name="vm-clone"><span class="text">$_("Clone")</span></button> >>> >+ <button class="button-big" name="vm-edit"><span class="text">$_("Edit")</span></button> >>> > <button class="button-big shutoff-hidden" name="vm-reset"><span class="text">$_("Reset")</span></button> >>> > <button class="button-big shutoff-hidden" name="vm-shutdown"><span class="text">$_("Shut Down")</span></button> >>> > <button class="button-big running-hidden" name="vm-start"><span class="text">$_("Start")</span></button> > > > > _______________________________________________ > Kimchi-devel mailing list > Kimchi-devel@ovirt.org > http://lists.ovirt.org/mailman/listinfo/kimchi-devel

On 10/29/2014 2:22 AM, Aline Manera wrote: > > On 10/28/2014 06:58 AM, Yu Xin Huo wrote: >> On 10/28/2014 3:18 AM, Paulo Ricardo Paz Vital wrote: >>> I'd like to do two suggestions: >>> 1) Add a progress bar just below the 'Cloning..." text to show the >>> cloning progress - libvirt provides this info; and >> I assume the progress is overall cloning progress. It is helpful, let >> me know where to get it from API response. > > I think it will come on Task message parameter in a same way we did > for upload/download. > But I will let Cristian confirm it. > >>> 2) Add a warning screen when user select to clone a VM informing that >>> the new virtual disk will be located in the default storage pool if the >>> VM is using a iSCSI storage pool and/or the storage pool of the VM to be >>> cloned doesn't have enough space in disk. It's a informative warning >>> that must be displayed every time that a VM is choosed to be cloned >>> (like when user select to execute a Power Off of some running guest). >> If it is just a static alert message, I think it is better to put it >> into help doc. >> 1. This alert message is only valuable when user first time clone a >> guest, once he/she got to know that, it is completely redundant for >> daily operations. >> 2. Clone is not an operation like 'Delete' that will lead to harmful, >> unrecoverable result. If user dislike it after cloning, he can delete >> the cloned guest. >> 3. The default clone is to pursue truly desired outcome for user to >> have a new guest with rid of any noisy distraction. >> >> As we have discussed, default clone is not functional enough and a >> 'Custom Clone' will be added. in 'Custom Clone' >> A dialog will popup with all customization options for user to >> select, including select the target pool for each virtual disk of the >> guest. > > I was talking to Leonardo and Paulo offline and they came up an > important point: > Kimchi must be easy to use - which also means the user should not need > to read any documentation to get it working as desired. Good point. Repeat again: "*the user should not need to read any documentation to get it working as desired*". 1. This can be used as a criteria to measure UI design. This need to be achieved with truly functional content & simplified user experience flow rather than moving those static help content to UI. 2. We should keep this in mind when designing any feature, not only 'Guest Clone'. > And as Paulo mentioned/proposed it will be an static warning, so we > don't need to have a clone pre-check or so. It would work in the same > way when we power off the guest. > Based on all that, I also agree with this warning message. > > Something like: > "When the target guest has SCSI or iSCSI volumes, they will be cloned > on default storage pool. The same will happen when the target pool > does not have enough space to clone the volumes. Do you want to continue?" About *Always* popup a *Static* message, 1. There are 5 types of pools, iSCSI and SCSI are 2 of them and run out of space is rare. Why always popup a static message(an additional step) with sacrificing frequent operations(dir/nfs/logical pool with enough space) for rare operations(scsi or run out of space)? 2. Web development has already come to the age of dynamic and responsive long ago with ajax and html5 technologies like web socket bi-directional communication. This "*Always* popup a *Static* message" is the experience that 1st generation static web page linking delivered. 3. From the content of this message itself, we see it is conditional, but it will always be prompted to user with the same content. If user is cloning a guest that have a volume in a 'dir' pool and that pool has enough space, why prompt such a message? is it confusing user? is it trigger user additional doubts that something wrong? 4. The most basic principle for message is specific which can get user to pinpoint the actual root cause. Obviously, this message has 2 causes, 1) SCSI or iSCSI. 2) the pool does not have enough space. I recommend below: 1. Do not popup any message, just run the clone with default flow. 2. *Conditional* popup a *dynamic* message with pre-check. If it is iscsi or scsi fc, then message that "the target guest has SCSI or iSCSI volumes, they will be cloned on default storage pool, Do you want to continue?" if pool run out of space, then message that "the target pool does not have enough space to clone the volumes, they will be cloned on default storage pool, Do you want to continue?" if not iscsi or scsi fc and enough space, just let the clone happen without any popup. I think this is a much, much better experience. > >>> I know that the second topic was discussed in previous scrum meetings, >>> but it's important alert the user about the backend operations. >>> >>> Thanks and best regards, >>> -- Paulo Ricardo Paz Vital <pvital@linux.vnet.ibm.com> IBM Linux >>> Technology Center >>>> >From: Yu Xin Huo<huoyuxin@linux.vnet.ibm.com> >>>> > >>>> >Signed-off-by: Yu Xin Huo<huoyuxin@linux.vnet.ibm.com> >>>> >--- >>>> > ui/css/theme-default/list.css | 18 ++++++++++ >>>> > ui/js/src/kimchi.api.js | 17 ++++++++- >>>> > ui/js/src/kimchi.guest_main.js | 73 +++++++++++++++++++++++++++++++++++---- >>>> > ui/pages/guest.html.tmpl | 6 +++- >>>> > 4 files changed, 103 insertions(+), 11 deletions(-) >>>> > >>>> >diff --git a/ui/css/theme-default/list.css b/ui/css/theme-default/list.css >>>> >index 8ffee69..fc3017b 100644 >>>> >--- a/ui/css/theme-default/list.css >>>> >+++ b/ui/css/theme-default/list.css >>>> >@@ -275,3 +275,21 @@ >>>> > text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff; >>>> > padding-left: 10px; >>>> > } >>>> >+ >>>> >+.guest-clone { >>>> >+ margin: 10px; >>>> >+} >>>> >+ >>>> >+.guest-clone .icon { >>>> >+ background: url('../../images/theme-default/loading.gif') no-repeat; >>>> >+ display: inline-block; >>>> >+ width: 20px; >>>> >+ height: 20px; >>>> >+ vertical-align: middle; >>>> >+} >>>> >+ >>>> >+.guest-clone .text { >>>> >+ color: #666666; >>>> >+ margin-left: 5px; >>>> >+ text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF; >>>> >+} >>>> >diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js >>>> >index 5895a07..2f90219 100644 >>>> >--- a/ui/js/src/kimchi.api.js >>>> >+++ b/ui/js/src/kimchi.api.js >>>> >@@ -695,10 +695,10 @@ var kimchi = { >>>> > }, 2000); >>>> > break; >>>> > case 'finished': >>>> >- suc(result); >>>> >+ suc && suc(result); >>>> > break; >>>> > case 'failed': >>>> >- err(result); >>>> >+ err && err(result); >>>> > break; >>>> > default: >>>> > break; >>>> >@@ -1233,5 +1233,18 @@ var kimchi = { >>>> > success : suc, >>>> > error : err >>>> > }); >>>> >+ }, >>>> >+ >>>> >+ cloneGuest: function(vm, suc, err) { >>>> >+ kimchi.requestJSON({ >>>> >+ url : kimchi.url + 'vms/'+encodeURIComponent(vm)+"/clone", >>>> >+ type : 'POST', >>>> >+ contentType : 'application/json', >>>> >+ dataType : 'json', >>>> >+ success : suc, >>>> >+ error : err ? err : function(data) { >>>> >+ kimchi.message.error(data.responseJSON.reason); >>>> >+ } >>>> >+ }); >>>> > } >>>> > }; >>>> >diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js >>>> >index dbe8753..ecc3b7a 100644 >>>> >--- a/ui/js/src/kimchi.guest_main.js >>>> >+++ b/ui/js/src/kimchi.guest_main.js >>>> >@@ -15,6 +15,34 @@ >>>> > * See the License for the specific language governing permissions and >>>> > * limitations under the License. >>>> > */ >>>> >+kimchi.sampleGuestObject = { >>>> >+ "name": "", >>>> >+ "uuid": "", >>>> >+ "state": "shutoff", >>>> >+ "persistent": true, >>>> >+ "icon": null, >>>> >+ "cpus": 0, >>>> >+ "memory": 0, >>>> >+ "stats": { >>>> >+ "net_throughput": 0, >>>> >+ "io_throughput_peak": 100, >>>> >+ "cpu_utilization": 0, >>>> >+ "io_throughput": 0, >>>> >+ "net_throughput_peak": 100 >>>> >+ }, >>>> >+ "screenshot": null, >>>> >+ "graphics": { >>>> >+ "passwd": null, >>>> >+ "passwdValidTo": null, >>>> >+ "type": "vnc", >>>> >+ "port": null, >>>> >+ "listen": "127.0.0.1" >>>> >+ }, >>>> >+ "users": [], >>>> >+ "groups": [], >>>> >+ "access": "full" >>>> >+}; >>>> >+ >>>> > >>>> > kimchi.vmstart = function(event) { >>>> > var button=$(this); >>>> >@@ -173,8 +201,24 @@ kimchi.listVmsAuto = function() { >>>> > if (kimchi.vmTimeout) { >>>> > clearTimeout(kimchi.vmTimeout); >>>> > } >>>> >+ var getCloningGuests = function(){ >>>> >+ var guests = []; >>>> >+ kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/*'), function(tasks) { >>>> >+ for(var i=0;i<tasks.length;i++){ >>>> >+ var guestUri = tasks[i].target_uri; >>>> >+ var guestName = guestUri.substring(guestUri.lastIndexOf('/')+1, guestUri.length); >>>> >+ guests.push($.extend({}, kimchi.sampleGuestObject, {name: guestName, isCloning: true})); >>>> >+ if(kimchi.trackingTasks.indexOf(tasks[i].id)==-1) >>>> >+ kimchi.trackTask(tasks[i].id, null, function(err){ >>>> >+ kimchi.message.error(err.message); >>>> >+ }, null); >>>> >+ } >>>> >+ }, null, true); >>>> >+ return guests; >>>> >+ }; >>>> > kimchi.listVMs(function(result, textStatus, jqXHR) { >>>> > if (result && textStatus=="success") { >>>> >+ result = getCloningGuests().concat(result); >>>> > if(result.length) { >>>> > var listHtml = ''; >>>> > var guestTemplate = kimchi.guestTemplate; >>>> >@@ -233,14 +277,16 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { >>>> > imgLoad.attr('src',load_src); >>>> > >>>> > //Link the stopped tile to the start action, the running tile to open the console >>>> >- if (vmRunningBool) { >>>> >- liveTile.off("click", kimchi.vmstart); >>>> >- liveTile.on("click", kimchi.openVmConsole); >>>> >- } >>>> >- else { >>>> >- liveTile.off("click", kimchi.openVmConsole); >>>> >- liveTile.on("click", kimchi.vmstart); >>>> >- liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); >>>> >+ if(!vmObject.isCloning){ >>>> >+ if (vmRunningBool) { >>>> >+ liveTile.off("click", kimchi.vmstart); >>>> >+ liveTile.on("click", kimchi.openVmConsole); >>>> >+ } >>>> >+ else { >>>> >+ liveTile.off("click", kimchi.openVmConsole); >>>> >+ liveTile.on("click", kimchi.vmstart); >>>> >+ liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); >>>> >+ } >>>> > } >>>> > >>>> > >>>> >@@ -257,6 +303,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { >>>> > //Setup the VM Actions >>>> > var guestActions=result.find("div[name=guest-actions]"); >>>> > guestActions.find(".shutoff-disabled").prop('disabled', !vmRunningBool ); >>>> >+ guestActions.find(".running-disabled").prop('disabled', vmRunningBool ); >>>> > >>>> > if (vmRunningBool) { >>>> > guestActions.find(".running-hidden").hide(); >>>> >@@ -286,6 +333,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { >>>> > } >>>> > guestActions.find("[name=vm-edit]").on({click : kimchi.vmedit}); >>>> > guestActions.find("[name=vm-delete]").on({click : kimchi.vmdelete}); >>>> >+ guestActions.find("[name=vm-clone]").click(function(){ >>>> >+ kimchi.cloneGuest($(this).closest('li[name=guest]').attr("id"), function(data){ >>>> >+ kimchi.listVmsAuto(); >>>> >+ }); >>>> >+ }); >>>> > >>>> > //Maintain menu open state >>>> > var actionMenu=guestActions.find("div[name=actionmenu]"); >>>> >@@ -293,6 +345,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { >>>> > $('.popover', actionMenu).toggle(); >>>> > } >>>> > >>>> >+ if(vmObject.isCloning){ >>>> >+ guestActions.children().hide(); >>>> >+ result.find('.guest-clone').removeClass('hide-content'); >>>> >+ } >>>> >+ >>>> > return result; >>>> > }; >>>> > >>>> >diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl >>>> >index 43fb350..74206fd 100644 >>>> >--- a/ui/pages/guest.html.tmpl >>>> >+++ b/ui/pages/guest.html.tmpl >>>> >@@ -26,6 +26,9 @@ >>>> > <div class="guest-general"> >>>> > <h2 class="title" title="{name}">{name}</h2> >>>> > </div> >>>> >+ <div class="guest-clone hide-content"> >>>> >+ <span class="icon"></span><span class="text">$_("Cloning")...</span> >>>> >+ </div> >>>> > </div> >>>> > <div name="cpu_utilization" class="sortable"> >>>> > <div class="circleGauge"></div> >>>> >@@ -56,7 +59,8 @@ >>>> > <span class="text">$_("Actions")</span><span class="arrow"></span> >>>> > <div class="popover actionsheet right-side" style="width: 250px"> >>>> > <button class="button-big shutoff-disabled" name="vm-console" ><span class="text">$_("Connect")</span></button> >>>> >- <button class="button-big running-disabled" name="vm-edit"><span class="text">$_("Edit")</span></button> >>>> >+ <button class="button-big running-disabled" name="vm-clone"><span class="text">$_("Clone")</span></button> >>>> >+ <button class="button-big" name="vm-edit"><span class="text">$_("Edit")</span></button> >>>> > <button class="button-big shutoff-hidden" name="vm-reset"><span class="text">$_("Reset")</span></button> >>>> > <button class="button-big shutoff-hidden" name="vm-shutdown"><span class="text">$_("Shut Down")</span></button> >>>> > <button class="button-big running-hidden" name="vm-start"><span class="text">$_("Start")</span></button> >> >> >> >> _______________________________________________ >> Kimchi-devel mailing list >> Kimchi-devel@ovirt.org >> http://lists.ovirt.org/mailman/listinfo/kimchi-devel >

Some comments/suggestions: 1) Instead of displaying the default icon in the new entry, we could get that information from the target guest and create a new input using it. So the cloning entry will have the same icon from the target guest. 2) I'd suggest to display the actions buttons disabled instead of removing them from the user view. 3) +1 for the progress bar suggested by Paulo 4) +1 for the static warning message also suggested by Paulo (see my other email for details) On 10/24/2014 07:15 AM, huoyuxin@linux.vnet.ibm.com wrote:
From: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com>
Signed-off-by: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> --- ui/css/theme-default/list.css | 18 ++++++++++ ui/js/src/kimchi.api.js | 17 ++++++++- ui/js/src/kimchi.guest_main.js | 73 +++++++++++++++++++++++++++++++++++---- ui/pages/guest.html.tmpl | 6 +++- 4 files changed, 103 insertions(+), 11 deletions(-)
diff --git a/ui/css/theme-default/list.css b/ui/css/theme-default/list.css index 8ffee69..fc3017b 100644 --- a/ui/css/theme-default/list.css +++ b/ui/css/theme-default/list.css @@ -275,3 +275,21 @@ text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff; padding-left: 10px; } + +.guest-clone { + margin: 10px; +} + +.guest-clone .icon { + background: url('../../images/theme-default/loading.gif') no-repeat; + display: inline-block; + width: 20px; + height: 20px; + vertical-align: middle; +} + +.guest-clone .text { + color: #666666; + margin-left: 5px; + text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF; +} diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js index 5895a07..2f90219 100644 --- a/ui/js/src/kimchi.api.js +++ b/ui/js/src/kimchi.api.js @@ -695,10 +695,10 @@ var kimchi = { }, 2000); break; case 'finished': - suc(result); + suc && suc(result); break; case 'failed': - err(result); + err && err(result); break; default: break; @@ -1233,5 +1233,18 @@ var kimchi = { success : suc, error : err }); + }, + + cloneGuest: function(vm, suc, err) { + kimchi.requestJSON({ + url : kimchi.url + 'vms/'+encodeURIComponent(vm)+"/clone", + type : 'POST', + contentType : 'application/json', + dataType : 'json', + success : suc, + error : err ? err : function(data) { + kimchi.message.error(data.responseJSON.reason); + } + }); } }; diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js index dbe8753..ecc3b7a 100644 --- a/ui/js/src/kimchi.guest_main.js +++ b/ui/js/src/kimchi.guest_main.js @@ -15,6 +15,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +kimchi.sampleGuestObject = { + "name": "", + "uuid": "", + "state": "shutoff", + "persistent": true, + "icon": null, + "cpus": 0, + "memory": 0, + "stats": { + "net_throughput": 0, + "io_throughput_peak": 100, + "cpu_utilization": 0, + "io_throughput": 0, + "net_throughput_peak": 100 + }, + "screenshot": null, + "graphics": { + "passwd": null, + "passwdValidTo": null, + "type": "vnc", + "port": null, + "listen": "127.0.0.1" + }, + "users": [], + "groups": [], + "access": "full" +}; +
kimchi.vmstart = function(event) { var button=$(this); @@ -173,8 +201,24 @@ kimchi.listVmsAuto = function() { if (kimchi.vmTimeout) { clearTimeout(kimchi.vmTimeout); } + var getCloningGuests = function(){ + var guests = []; + kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/*'), function(tasks) { + for(var i=0;i<tasks.length;i++){ + var guestUri = tasks[i].target_uri; + var guestName = guestUri.substring(guestUri.lastIndexOf('/')+1, guestUri.length); + guests.push($.extend({}, kimchi.sampleGuestObject, {name: guestName, isCloning: true})); + if(kimchi.trackingTasks.indexOf(tasks[i].id)==-1) + kimchi.trackTask(tasks[i].id, null, function(err){ + kimchi.message.error(err.message); + }, null); + } + }, null, true); + return guests; + }; kimchi.listVMs(function(result, textStatus, jqXHR) { if (result && textStatus=="success") { + result = getCloningGuests().concat(result); if(result.length) { var listHtml = ''; var guestTemplate = kimchi.guestTemplate; @@ -233,14 +277,16 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { imgLoad.attr('src',load_src);
//Link the stopped tile to the start action, the running tile to open the console - if (vmRunningBool) { - liveTile.off("click", kimchi.vmstart); - liveTile.on("click", kimchi.openVmConsole); - } - else { - liveTile.off("click", kimchi.openVmConsole); - liveTile.on("click", kimchi.vmstart); - liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); + if(!vmObject.isCloning){ + if (vmRunningBool) { + liveTile.off("click", kimchi.vmstart); + liveTile.on("click", kimchi.openVmConsole); + } + else { + liveTile.off("click", kimchi.openVmConsole); + liveTile.on("click", kimchi.vmstart); + liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); + } }
@@ -257,6 +303,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { //Setup the VM Actions var guestActions=result.find("div[name=guest-actions]"); guestActions.find(".shutoff-disabled").prop('disabled', !vmRunningBool ); + guestActions.find(".running-disabled").prop('disabled', vmRunningBool );
if (vmRunningBool) { guestActions.find(".running-hidden").hide(); @@ -286,6 +333,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { } guestActions.find("[name=vm-edit]").on({click : kimchi.vmedit}); guestActions.find("[name=vm-delete]").on({click : kimchi.vmdelete}); + guestActions.find("[name=vm-clone]").click(function(){ + kimchi.cloneGuest($(this).closest('li[name=guest]').attr("id"), function(data){ + kimchi.listVmsAuto(); + }); + });
//Maintain menu open state var actionMenu=guestActions.find("div[name=actionmenu]"); @@ -293,6 +345,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { $('.popover', actionMenu).toggle(); }
+ if(vmObject.isCloning){ + guestActions.children().hide(); + result.find('.guest-clone').removeClass('hide-content'); + } + return result; };
diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl index 43fb350..74206fd 100644 --- a/ui/pages/guest.html.tmpl +++ b/ui/pages/guest.html.tmpl @@ -26,6 +26,9 @@ <div class="guest-general"> <h2 class="title" title="{name}">{name}</h2> </div> + <div class="guest-clone hide-content"> + <span class="icon"></span><span class="text">$_("Cloning")...</span> + </div> </div> <div name="cpu_utilization" class="sortable"> <div class="circleGauge"></div> @@ -56,7 +59,8 @@ <span class="text">$_("Actions")</span><span class="arrow"></span> <div class="popover actionsheet right-side" style="width: 250px"> <button class="button-big shutoff-disabled" name="vm-console" ><span class="text">$_("Connect")</span></button> - <button class="button-big running-disabled" name="vm-edit"><span class="text">$_("Edit")</span></button> + <button class="button-big running-disabled" name="vm-clone"><span class="text">$_("Clone")</span></button> + <button class="button-big" name="vm-edit"><span class="text">$_("Edit")</span></button> <button class="button-big shutoff-hidden" name="vm-reset"><span class="text">$_("Reset")</span></button> <button class="button-big shutoff-hidden" name="vm-shutdown"><span class="text">$_("Shut Down")</span></button> <button class="button-big running-hidden" name="vm-start"><span class="text">$_("Start")</span></button>

On Tue, 2014-10-28 at 16:39 -0200, Aline Manera wrote:
Some comments/suggestions:
1) Instead of displaying the default icon in the new entry, we could get that information from the target guest and create a new input using it. So the cloning entry will have the same icon from the target guest.
+1
2) I'd suggest to display the actions buttons disabled instead of removing them from the user view.
+1
3) +1 for the progress bar suggested by Paulo
4) +1 for the static warning message also suggested by Paulo (see my other email for details)
On 10/24/2014 07:15 AM, huoyuxin@linux.vnet.ibm.com wrote:
From: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com>
Signed-off-by: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> --- ui/css/theme-default/list.css | 18 ++++++++++ ui/js/src/kimchi.api.js | 17 ++++++++- ui/js/src/kimchi.guest_main.js | 73 +++++++++++++++++++++++++++++++++++---- ui/pages/guest.html.tmpl | 6 +++- 4 files changed, 103 insertions(+), 11 deletions(-)
diff --git a/ui/css/theme-default/list.css b/ui/css/theme-default/list.css index 8ffee69..fc3017b 100644 --- a/ui/css/theme-default/list.css +++ b/ui/css/theme-default/list.css @@ -275,3 +275,21 @@ text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff; padding-left: 10px; } + +.guest-clone { + margin: 10px; +} + +.guest-clone .icon { + background: url('../../images/theme-default/loading.gif') no-repeat; + display: inline-block; + width: 20px; + height: 20px; + vertical-align: middle; +} + +.guest-clone .text { + color: #666666; + margin-left: 5px; + text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF; +} diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js index 5895a07..2f90219 100644 --- a/ui/js/src/kimchi.api.js +++ b/ui/js/src/kimchi.api.js @@ -695,10 +695,10 @@ var kimchi = { }, 2000); break; case 'finished': - suc(result); + suc && suc(result); break; case 'failed': - err(result); + err && err(result); break; default: break; @@ -1233,5 +1233,18 @@ var kimchi = { success : suc, error : err }); + }, + + cloneGuest: function(vm, suc, err) { + kimchi.requestJSON({ + url : kimchi.url + 'vms/'+encodeURIComponent(vm)+"/clone", + type : 'POST', + contentType : 'application/json', + dataType : 'json', + success : suc, + error : err ? err : function(data) { + kimchi.message.error(data.responseJSON.reason); + } + }); } }; diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js index dbe8753..ecc3b7a 100644 --- a/ui/js/src/kimchi.guest_main.js +++ b/ui/js/src/kimchi.guest_main.js @@ -15,6 +15,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +kimchi.sampleGuestObject = { + "name": "", + "uuid": "", + "state": "shutoff", + "persistent": true, + "icon": null, + "cpus": 0, + "memory": 0, + "stats": { + "net_throughput": 0, + "io_throughput_peak": 100, + "cpu_utilization": 0, + "io_throughput": 0, + "net_throughput_peak": 100 + }, + "screenshot": null, + "graphics": { + "passwd": null, + "passwdValidTo": null, + "type": "vnc", + "port": null, + "listen": "127.0.0.1" + }, + "users": [], + "groups": [], + "access": "full" +}; +
kimchi.vmstart = function(event) { var button=$(this); @@ -173,8 +201,24 @@ kimchi.listVmsAuto = function() { if (kimchi.vmTimeout) { clearTimeout(kimchi.vmTimeout); } + var getCloningGuests = function(){ + var guests = []; + kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/*'), function(tasks) { + for(var i=0;i<tasks.length;i++){ + var guestUri = tasks[i].target_uri; + var guestName = guestUri.substring(guestUri.lastIndexOf('/')+1, guestUri.length); + guests.push($.extend({}, kimchi.sampleGuestObject, {name: guestName, isCloning: true})); + if(kimchi.trackingTasks.indexOf(tasks[i].id)==-1) + kimchi.trackTask(tasks[i].id, null, function(err){ + kimchi.message.error(err.message); + }, null); + } + }, null, true); + return guests; + }; kimchi.listVMs(function(result, textStatus, jqXHR) { if (result && textStatus=="success") { + result = getCloningGuests().concat(result); if(result.length) { var listHtml = ''; var guestTemplate = kimchi.guestTemplate; @@ -233,14 +277,16 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { imgLoad.attr('src',load_src);
//Link the stopped tile to the start action, the running tile to open the console - if (vmRunningBool) { - liveTile.off("click", kimchi.vmstart); - liveTile.on("click", kimchi.openVmConsole); - } - else { - liveTile.off("click", kimchi.openVmConsole); - liveTile.on("click", kimchi.vmstart); - liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); + if(!vmObject.isCloning){ + if (vmRunningBool) { + liveTile.off("click", kimchi.vmstart); + liveTile.on("click", kimchi.openVmConsole); + } + else { + liveTile.off("click", kimchi.openVmConsole); + liveTile.on("click", kimchi.vmstart); + liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); + } }
@@ -257,6 +303,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { //Setup the VM Actions var guestActions=result.find("div[name=guest-actions]"); guestActions.find(".shutoff-disabled").prop('disabled', !vmRunningBool ); + guestActions.find(".running-disabled").prop('disabled', vmRunningBool );
if (vmRunningBool) { guestActions.find(".running-hidden").hide(); @@ -286,6 +333,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { } guestActions.find("[name=vm-edit]").on({click : kimchi.vmedit}); guestActions.find("[name=vm-delete]").on({click : kimchi.vmdelete}); + guestActions.find("[name=vm-clone]").click(function(){ + kimchi.cloneGuest($(this).closest('li[name=guest]').attr("id"), function(data){ + kimchi.listVmsAuto(); + }); + });
//Maintain menu open state var actionMenu=guestActions.find("div[name=actionmenu]"); @@ -293,6 +345,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { $('.popover', actionMenu).toggle(); }
+ if(vmObject.isCloning){ + guestActions.children().hide(); + result.find('.guest-clone').removeClass('hide-content'); + } + return result; };
diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl index 43fb350..74206fd 100644 --- a/ui/pages/guest.html.tmpl +++ b/ui/pages/guest.html.tmpl @@ -26,6 +26,9 @@ <div class="guest-general"> <h2 class="title" title="{name}">{name}</h2> </div> + <div class="guest-clone hide-content"> + <span class="icon"></span><span class="text">$_("Cloning")...</span> + </div> </div> <div name="cpu_utilization" class="sortable"> <div class="circleGauge"></div> @@ -56,7 +59,8 @@ <span class="text">$_("Actions")</span><span class="arrow"></span> <div class="popover actionsheet right-side" style="width: 250px"> <button class="button-big shutoff-disabled" name="vm-console" ><span class="text">$_("Connect")</span></button> - <button class="button-big running-disabled" name="vm-edit"><span class="text">$_("Edit")</span></button> + <button class="button-big running-disabled" name="vm-clone"><span class="text">$_("Clone")</span></button> + <button class="button-big" name="vm-edit"><span class="text">$_("Edit")</span></button> <button class="button-big shutoff-hidden" name="vm-reset"><span class="text">$_("Reset")</span></button> <button class="button-big shutoff-hidden" name="vm-shutdown"><span class="text">$_("Shut Down")</span></button> <button class="button-big running-hidden" name="vm-start"><span class="text">$_("Start")</span></button>
_______________________________________________ Kimchi-devel mailing list Kimchi-devel@ovirt.org http://lists.ovirt.org/mailman/listinfo/kimchi-devel

On 10/29/2014 2:39 AM, Aline Manera wrote:
Some comments/suggestions:
1) Instead of displaying the default icon in the new entry, we could get that information from the target guest and create a new input using it. So the cloning entry will have the same icon from the target guest.
For current long task design, the ongoing tasks will survive 'page switch', 'page refresh', 'logout/login'. So all tasks should be tracked only with information from '/tasks' API. Currently, in the response of '/tasks' API, there is no reliable way to get the original guest that is cloned from. Unless a reliable way to get the original target guest from '/tasks', we can only keep live tile to be default.
2) I'd suggest to display the actions buttons disabled instead of removing them from the user view.
3) +1 for the progress bar suggested by Paulo
4) +1 for the static warning message also suggested by Paulo (see my other email for details)
On 10/24/2014 07:15 AM, huoyuxin@linux.vnet.ibm.com wrote:
From: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com>
Signed-off-by: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> --- ui/css/theme-default/list.css | 18 ++++++++++ ui/js/src/kimchi.api.js | 17 ++++++++- ui/js/src/kimchi.guest_main.js | 73 +++++++++++++++++++++++++++++++++++---- ui/pages/guest.html.tmpl | 6 +++- 4 files changed, 103 insertions(+), 11 deletions(-)
diff --git a/ui/css/theme-default/list.css b/ui/css/theme-default/list.css index 8ffee69..fc3017b 100644 --- a/ui/css/theme-default/list.css +++ b/ui/css/theme-default/list.css @@ -275,3 +275,21 @@ text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff; padding-left: 10px; } + +.guest-clone { + margin: 10px; +} + +.guest-clone .icon { + background: url('../../images/theme-default/loading.gif') no-repeat; + display: inline-block; + width: 20px; + height: 20px; + vertical-align: middle; +} + +.guest-clone .text { + color: #666666; + margin-left: 5px; + text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF; +} diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js index 5895a07..2f90219 100644 --- a/ui/js/src/kimchi.api.js +++ b/ui/js/src/kimchi.api.js @@ -695,10 +695,10 @@ var kimchi = { }, 2000); break; case 'finished': - suc(result); + suc && suc(result); break; case 'failed': - err(result); + err && err(result); break; default: break; @@ -1233,5 +1233,18 @@ var kimchi = { success : suc, error : err }); + }, + + cloneGuest: function(vm, suc, err) { + kimchi.requestJSON({ + url : kimchi.url + 'vms/'+encodeURIComponent(vm)+"/clone", + type : 'POST', + contentType : 'application/json', + dataType : 'json', + success : suc, + error : err ? err : function(data) { + kimchi.message.error(data.responseJSON.reason); + } + }); } }; diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js index dbe8753..ecc3b7a 100644 --- a/ui/js/src/kimchi.guest_main.js +++ b/ui/js/src/kimchi.guest_main.js @@ -15,6 +15,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +kimchi.sampleGuestObject = { + "name": "", + "uuid": "", + "state": "shutoff", + "persistent": true, + "icon": null, + "cpus": 0, + "memory": 0, + "stats": { + "net_throughput": 0, + "io_throughput_peak": 100, + "cpu_utilization": 0, + "io_throughput": 0, + "net_throughput_peak": 100 + }, + "screenshot": null, + "graphics": { + "passwd": null, + "passwdValidTo": null, + "type": "vnc", + "port": null, + "listen": "127.0.0.1" + }, + "users": [], + "groups": [], + "access": "full" +}; +
kimchi.vmstart = function(event) { var button=$(this); @@ -173,8 +201,24 @@ kimchi.listVmsAuto = function() { if (kimchi.vmTimeout) { clearTimeout(kimchi.vmTimeout); } + var getCloningGuests = function(){ + var guests = []; + kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/*'), function(tasks) { + for(var i=0;i<tasks.length;i++){ + var guestUri = tasks[i].target_uri; + var guestName = guestUri.substring(guestUri.lastIndexOf('/')+1, guestUri.length); + guests.push($.extend({}, kimchi.sampleGuestObject, {name: guestName, isCloning: true})); + if(kimchi.trackingTasks.indexOf(tasks[i].id)==-1) + kimchi.trackTask(tasks[i].id, null, function(err){ + kimchi.message.error(err.message); + }, null); + } + }, null, true); + return guests; + }; kimchi.listVMs(function(result, textStatus, jqXHR) { if (result && textStatus=="success") { + result = getCloningGuests().concat(result); if(result.length) { var listHtml = ''; var guestTemplate = kimchi.guestTemplate; @@ -233,14 +277,16 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { imgLoad.attr('src',load_src);
//Link the stopped tile to the start action, the running tile to open the console - if (vmRunningBool) { - liveTile.off("click", kimchi.vmstart); - liveTile.on("click", kimchi.openVmConsole); - } - else { - liveTile.off("click", kimchi.openVmConsole); - liveTile.on("click", kimchi.vmstart); - liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); + if(!vmObject.isCloning){ + if (vmRunningBool) { + liveTile.off("click", kimchi.vmstart); + liveTile.on("click", kimchi.openVmConsole); + } + else { + liveTile.off("click", kimchi.openVmConsole); + liveTile.on("click", kimchi.vmstart); + liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); + } }
@@ -257,6 +303,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { //Setup the VM Actions var guestActions=result.find("div[name=guest-actions]"); guestActions.find(".shutoff-disabled").prop('disabled', !vmRunningBool ); + guestActions.find(".running-disabled").prop('disabled', vmRunningBool );
if (vmRunningBool) { guestActions.find(".running-hidden").hide(); @@ -286,6 +333,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { } guestActions.find("[name=vm-edit]").on({click : kimchi.vmedit}); guestActions.find("[name=vm-delete]").on({click : kimchi.vmdelete}); + guestActions.find("[name=vm-clone]").click(function(){ + kimchi.cloneGuest($(this).closest('li[name=guest]').attr("id"), function(data){ + kimchi.listVmsAuto(); + }); + });
//Maintain menu open state var actionMenu=guestActions.find("div[name=actionmenu]"); @@ -293,6 +345,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { $('.popover', actionMenu).toggle(); }
+ if(vmObject.isCloning){ + guestActions.children().hide(); + result.find('.guest-clone').removeClass('hide-content'); + } + return result; };
diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl index 43fb350..74206fd 100644 --- a/ui/pages/guest.html.tmpl +++ b/ui/pages/guest.html.tmpl @@ -26,6 +26,9 @@ <div class="guest-general"> <h2 class="title" title="{name}">{name}</h2> </div> + <div class="guest-clone hide-content"> + <span class="icon"></span><span class="text">$_("Cloning")...</span> + </div> </div> <div name="cpu_utilization" class="sortable"> <div class="circleGauge"></div> @@ -56,7 +59,8 @@ <span class="text">$_("Actions")</span><span class="arrow"></span> <div class="popover actionsheet right-side" style="width: 250px"> <button class="button-big shutoff-disabled" name="vm-console" ><span class="text">$_("Connect")</span></button> - <button class="button-big running-disabled" name="vm-edit"><span class="text">$_("Edit")</span></button> + <button class="button-big running-disabled" name="vm-clone"><span class="text">$_("Clone")</span></button> + <button class="button-big" name="vm-edit"><span class="text">$_("Edit")</span></button> <button class="button-big shutoff-hidden" name="vm-reset"><span class="text">$_("Reset")</span></button> <button class="button-big shutoff-hidden" name="vm-shutdown"><span class="text">$_("Shut Down")</span></button> <button class="button-big running-hidden" name="vm-start"><span class="text">$_("Start")</span></button>

On 10/30/2014 08:12 AM, Yu Xin Huo wrote:
On 10/29/2014 2:39 AM, Aline Manera wrote:
Some comments/suggestions:
1) Instead of displaying the default icon in the new entry, we could get that information from the target guest and create a new input using it. So the cloning entry will have the same icon from the target guest.
For current long task design, the ongoing tasks will survive 'page switch', 'page refresh', 'logout/login'. So all tasks should be tracked only with information from '/tasks' API. Currently, in the response of '/tasks' API, there is no reliable way to get the original guest that is cloned from.
Unless a reliable way to get the original target guest from '/tasks', we can only keep live tile to be default.
You are right! I had forgotten the switch tabs case. So keep using the default icon.
2) I'd suggest to display the actions buttons disabled instead of removing them from the user view.
3) +1 for the progress bar suggested by Paulo
4) +1 for the static warning message also suggested by Paulo (see my other email for details)
On 10/24/2014 07:15 AM, huoyuxin@linux.vnet.ibm.com wrote:
From: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com>
Signed-off-by: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> --- ui/css/theme-default/list.css | 18 ++++++++++ ui/js/src/kimchi.api.js | 17 ++++++++- ui/js/src/kimchi.guest_main.js | 73 +++++++++++++++++++++++++++++++++++---- ui/pages/guest.html.tmpl | 6 +++- 4 files changed, 103 insertions(+), 11 deletions(-)
diff --git a/ui/css/theme-default/list.css b/ui/css/theme-default/list.css index 8ffee69..fc3017b 100644 --- a/ui/css/theme-default/list.css +++ b/ui/css/theme-default/list.css @@ -275,3 +275,21 @@ text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff; padding-left: 10px; } + +.guest-clone { + margin: 10px; +} + +.guest-clone .icon { + background: url('../../images/theme-default/loading.gif') no-repeat; + display: inline-block; + width: 20px; + height: 20px; + vertical-align: middle; +} + +.guest-clone .text { + color: #666666; + margin-left: 5px; + text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF; +} diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js index 5895a07..2f90219 100644 --- a/ui/js/src/kimchi.api.js +++ b/ui/js/src/kimchi.api.js @@ -695,10 +695,10 @@ var kimchi = { }, 2000); break; case 'finished': - suc(result); + suc && suc(result); break; case 'failed': - err(result); + err && err(result); break; default: break; @@ -1233,5 +1233,18 @@ var kimchi = { success : suc, error : err }); + }, + + cloneGuest: function(vm, suc, err) { + kimchi.requestJSON({ + url : kimchi.url + 'vms/'+encodeURIComponent(vm)+"/clone", + type : 'POST', + contentType : 'application/json', + dataType : 'json', + success : suc, + error : err ? err : function(data) { + kimchi.message.error(data.responseJSON.reason); + } + }); } }; diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js index dbe8753..ecc3b7a 100644 --- a/ui/js/src/kimchi.guest_main.js +++ b/ui/js/src/kimchi.guest_main.js @@ -15,6 +15,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +kimchi.sampleGuestObject = { + "name": "", + "uuid": "", + "state": "shutoff", + "persistent": true, + "icon": null, + "cpus": 0, + "memory": 0, + "stats": { + "net_throughput": 0, + "io_throughput_peak": 100, + "cpu_utilization": 0, + "io_throughput": 0, + "net_throughput_peak": 100 + }, + "screenshot": null, + "graphics": { + "passwd": null, + "passwdValidTo": null, + "type": "vnc", + "port": null, + "listen": "127.0.0.1" + }, + "users": [], + "groups": [], + "access": "full" +}; +
kimchi.vmstart = function(event) { var button=$(this); @@ -173,8 +201,24 @@ kimchi.listVmsAuto = function() { if (kimchi.vmTimeout) { clearTimeout(kimchi.vmTimeout); } + var getCloningGuests = function(){ + var guests = []; + kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/*'), function(tasks) { + for(var i=0;i<tasks.length;i++){ + var guestUri = tasks[i].target_uri; + var guestName = guestUri.substring(guestUri.lastIndexOf('/')+1, guestUri.length); + guests.push($.extend({}, kimchi.sampleGuestObject, {name: guestName, isCloning: true})); + if(kimchi.trackingTasks.indexOf(tasks[i].id)==-1) + kimchi.trackTask(tasks[i].id, null, function(err){ + kimchi.message.error(err.message); + }, null); + } + }, null, true); + return guests; + }; kimchi.listVMs(function(result, textStatus, jqXHR) { if (result && textStatus=="success") { + result = getCloningGuests().concat(result); if(result.length) { var listHtml = ''; var guestTemplate = kimchi.guestTemplate; @@ -233,14 +277,16 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { imgLoad.attr('src',load_src);
//Link the stopped tile to the start action, the running tile to open the console - if (vmRunningBool) { - liveTile.off("click", kimchi.vmstart); - liveTile.on("click", kimchi.openVmConsole); - } - else { - liveTile.off("click", kimchi.openVmConsole); - liveTile.on("click", kimchi.vmstart); - liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); + if(!vmObject.isCloning){ + if (vmRunningBool) { + liveTile.off("click", kimchi.vmstart); + liveTile.on("click", kimchi.openVmConsole); + } + else { + liveTile.off("click", kimchi.openVmConsole); + liveTile.on("click", kimchi.vmstart); + liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); + } }
@@ -257,6 +303,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { //Setup the VM Actions var guestActions=result.find("div[name=guest-actions]"); guestActions.find(".shutoff-disabled").prop('disabled', !vmRunningBool ); + guestActions.find(".running-disabled").prop('disabled', vmRunningBool );
if (vmRunningBool) { guestActions.find(".running-hidden").hide(); @@ -286,6 +333,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { } guestActions.find("[name=vm-edit]").on({click : kimchi.vmedit}); guestActions.find("[name=vm-delete]").on({click : kimchi.vmdelete}); + guestActions.find("[name=vm-clone]").click(function(){ + kimchi.cloneGuest($(this).closest('li[name=guest]').attr("id"), function(data){ + kimchi.listVmsAuto(); + }); + });
//Maintain menu open state var actionMenu=guestActions.find("div[name=actionmenu]"); @@ -293,6 +345,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { $('.popover', actionMenu).toggle(); }
+ if(vmObject.isCloning){ + guestActions.children().hide(); + result.find('.guest-clone').removeClass('hide-content'); + } + return result; };
diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl index 43fb350..74206fd 100644 --- a/ui/pages/guest.html.tmpl +++ b/ui/pages/guest.html.tmpl @@ -26,6 +26,9 @@ <div class="guest-general"> <h2 class="title" title="{name}">{name}</h2> </div> + <div class="guest-clone hide-content"> + <span class="icon"></span><span class="text">$_("Cloning")...</span> + </div> </div> <div name="cpu_utilization" class="sortable"> <div class="circleGauge"></div> @@ -56,7 +59,8 @@ <span class="text">$_("Actions")</span><span class="arrow"></span> <div class="popover actionsheet right-side" style="width: 250px"> <button class="button-big shutoff-disabled" name="vm-console" ><span class="text">$_("Connect")</span></button> - <button class="button-big running-disabled" name="vm-edit"><span class="text">$_("Edit")</span></button> + <button class="button-big running-disabled" name="vm-clone"><span class="text">$_("Clone")</span></button> + <button class="button-big" name="vm-edit"><span class="text">$_("Edit")</span></button> <button class="button-big shutoff-hidden" name="vm-reset"><span class="text">$_("Reset")</span></button> <button class="button-big shutoff-hidden" name="vm-shutdown"><span class="text">$_("Shut Down")</span></button> <button class="button-big running-hidden" name="vm-start"><span class="text">$_("Start")</span></button>

On 10/29/2014 2:39 AM, Aline Manera wrote:
Some comments/suggestions:
1) Instead of displaying the default icon in the new entry, we could get that information from the target guest and create a new input using it. So the cloning entry will have the same icon from the target guest.
2) I'd suggest to display the actions buttons disabled instead of removing them from the user view.
All those buttons there currently has not defined a 'disabled' state, new icons with gray color will be needed to express a disabled state. I will try to explore some icon tool to see whether I make it.
3) +1 for the progress bar suggested by Paulo
4) +1 for the static warning message also suggested by Paulo (see my other email for details)
On 10/24/2014 07:15 AM, huoyuxin@linux.vnet.ibm.com wrote:
From: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com>
Signed-off-by: Yu Xin Huo <huoyuxin@linux.vnet.ibm.com> --- ui/css/theme-default/list.css | 18 ++++++++++ ui/js/src/kimchi.api.js | 17 ++++++++- ui/js/src/kimchi.guest_main.js | 73 +++++++++++++++++++++++++++++++++++---- ui/pages/guest.html.tmpl | 6 +++- 4 files changed, 103 insertions(+), 11 deletions(-)
diff --git a/ui/css/theme-default/list.css b/ui/css/theme-default/list.css index 8ffee69..fc3017b 100644 --- a/ui/css/theme-default/list.css +++ b/ui/css/theme-default/list.css @@ -275,3 +275,21 @@ text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff; padding-left: 10px; } + +.guest-clone { + margin: 10px; +} + +.guest-clone .icon { + background: url('../../images/theme-default/loading.gif') no-repeat; + display: inline-block; + width: 20px; + height: 20px; + vertical-align: middle; +} + +.guest-clone .text { + color: #666666; + margin-left: 5px; + text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF; +} diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js index 5895a07..2f90219 100644 --- a/ui/js/src/kimchi.api.js +++ b/ui/js/src/kimchi.api.js @@ -695,10 +695,10 @@ var kimchi = { }, 2000); break; case 'finished': - suc(result); + suc && suc(result); break; case 'failed': - err(result); + err && err(result); break; default: break; @@ -1233,5 +1233,18 @@ var kimchi = { success : suc, error : err }); + }, + + cloneGuest: function(vm, suc, err) { + kimchi.requestJSON({ + url : kimchi.url + 'vms/'+encodeURIComponent(vm)+"/clone", + type : 'POST', + contentType : 'application/json', + dataType : 'json', + success : suc, + error : err ? err : function(data) { + kimchi.message.error(data.responseJSON.reason); + } + }); } }; diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js index dbe8753..ecc3b7a 100644 --- a/ui/js/src/kimchi.guest_main.js +++ b/ui/js/src/kimchi.guest_main.js @@ -15,6 +15,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +kimchi.sampleGuestObject = { + "name": "", + "uuid": "", + "state": "shutoff", + "persistent": true, + "icon": null, + "cpus": 0, + "memory": 0, + "stats": { + "net_throughput": 0, + "io_throughput_peak": 100, + "cpu_utilization": 0, + "io_throughput": 0, + "net_throughput_peak": 100 + }, + "screenshot": null, + "graphics": { + "passwd": null, + "passwdValidTo": null, + "type": "vnc", + "port": null, + "listen": "127.0.0.1" + }, + "users": [], + "groups": [], + "access": "full" +}; +
kimchi.vmstart = function(event) { var button=$(this); @@ -173,8 +201,24 @@ kimchi.listVmsAuto = function() { if (kimchi.vmTimeout) { clearTimeout(kimchi.vmTimeout); } + var getCloningGuests = function(){ + var guests = []; + kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/vms/*'), function(tasks) { + for(var i=0;i<tasks.length;i++){ + var guestUri = tasks[i].target_uri; + var guestName = guestUri.substring(guestUri.lastIndexOf('/')+1, guestUri.length); + guests.push($.extend({}, kimchi.sampleGuestObject, {name: guestName, isCloning: true})); + if(kimchi.trackingTasks.indexOf(tasks[i].id)==-1) + kimchi.trackTask(tasks[i].id, null, function(err){ + kimchi.message.error(err.message); + }, null); + } + }, null, true); + return guests; + }; kimchi.listVMs(function(result, textStatus, jqXHR) { if (result && textStatus=="success") { + result = getCloningGuests().concat(result); if(result.length) { var listHtml = ''; var guestTemplate = kimchi.guestTemplate; @@ -233,14 +277,16 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { imgLoad.attr('src',load_src);
//Link the stopped tile to the start action, the running tile to open the console - if (vmRunningBool) { - liveTile.off("click", kimchi.vmstart); - liveTile.on("click", kimchi.openVmConsole); - } - else { - liveTile.off("click", kimchi.openVmConsole); - liveTile.on("click", kimchi.vmstart); - liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); + if(!vmObject.isCloning){ + if (vmRunningBool) { + liveTile.off("click", kimchi.vmstart); + liveTile.on("click", kimchi.openVmConsole); + } + else { + liveTile.off("click", kimchi.openVmConsole); + liveTile.on("click", kimchi.vmstart); + liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); + } }
@@ -257,6 +303,7 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { //Setup the VM Actions var guestActions=result.find("div[name=guest-actions]"); guestActions.find(".shutoff-disabled").prop('disabled', !vmRunningBool ); + guestActions.find(".running-disabled").prop('disabled', vmRunningBool );
if (vmRunningBool) { guestActions.find(".running-hidden").hide(); @@ -286,6 +333,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { } guestActions.find("[name=vm-edit]").on({click : kimchi.vmedit}); guestActions.find("[name=vm-delete]").on({click : kimchi.vmdelete}); + guestActions.find("[name=vm-clone]").click(function(){ + kimchi.cloneGuest($(this).closest('li[name=guest]').attr("id"), function(data){ + kimchi.listVmsAuto(); + }); + });
//Maintain menu open state var actionMenu=guestActions.find("div[name=actionmenu]"); @@ -293,6 +345,11 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { $('.popover', actionMenu).toggle(); }
+ if(vmObject.isCloning){ + guestActions.children().hide(); + result.find('.guest-clone').removeClass('hide-content'); + } + return result; };
diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl index 43fb350..74206fd 100644 --- a/ui/pages/guest.html.tmpl +++ b/ui/pages/guest.html.tmpl @@ -26,6 +26,9 @@ <div class="guest-general"> <h2 class="title" title="{name}">{name}</h2> </div> + <div class="guest-clone hide-content"> + <span class="icon"></span><span class="text">$_("Cloning")...</span> + </div> </div> <div name="cpu_utilization" class="sortable"> <div class="circleGauge"></div> @@ -56,7 +59,8 @@ <span class="text">$_("Actions")</span><span class="arrow"></span> <div class="popover actionsheet right-side" style="width: 250px"> <button class="button-big shutoff-disabled" name="vm-console" ><span class="text">$_("Connect")</span></button> - <button class="button-big running-disabled" name="vm-edit"><span class="text">$_("Edit")</span></button> + <button class="button-big running-disabled" name="vm-clone"><span class="text">$_("Clone")</span></button> + <button class="button-big" name="vm-edit"><span class="text">$_("Edit")</span></button> <button class="button-big shutoff-hidden" name="vm-reset"><span class="text">$_("Reset")</span></button> <button class="button-big shutoff-hidden" name="vm-shutdown"><span class="text">$_("Shut Down")</span></button> <button class="button-big running-hidden" name="vm-start"><span class="text">$_("Start")</span></button>
participants (4)
-
Aline Manera
-
huoyuxin@linux.vnet.ibm.com
-
Paulo Ricardo Paz Vital
-
Yu Xin Huo