[PATCH] bug fix: Properly generate guest disks on VMTemplate
by Aline Manera
Commits cdd146a9 and 749abbf8 changed the way VMTemplate generates the
guest disks XML but, due a merge issue, they did not update the
to_vm_xml() function that was pointing to non-existing functions:
_get_iscsi_disks_xml() and _get_scsi_disks_xml()
Signed-off-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
---
src/kimchi/vmtemplate.py | 12 +-----------
1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/src/kimchi/vmtemplate.py b/src/kimchi/vmtemplate.py
index 4e5806f..19c8ea1 100644
--- a/src/kimchi/vmtemplate.py
+++ b/src/kimchi/vmtemplate.py
@@ -155,7 +155,6 @@ class VMTemplate(object):
# storage pool, so we cannot mix the types (scsi volumes vs img file)
storage_type = self._get_storage_type()
storage_path = self._get_storage_path()
- storage_type = self._get_storage_type()
base_disk_params = {'type': 'disk', 'disk': 'file',
'bus': self.info['disk_bus'], 'format': 'qcow2'}
@@ -315,16 +314,7 @@ class VMTemplate(object):
graphics = kwargs.get('graphics')
params['graphics'] = self._get_graphics_xml(graphics)
params['cpu_info'] = self._get_cpu_xml()
-
- # Current implementation just allows to create disk in one single
- # storage pool, so we cannot mix the types (scsi volumes vs img file)
- storage_type = self._get_storage_type()
- if storage_type == "iscsi":
- params['disks'] = self._get_iscsi_disks_xml()
- elif storage_type == "scsi":
- params['disks'] = self._get_scsi_disks_xml()
- else:
- params['disks'] = self._get_disks_xml(vm_uuid)
+ params['disks'] = self._get_disks_xml(vm_uuid)
qemu_stream_dns = kwargs.get('qemu_stream_dns', False)
libvirt_stream_protocols = kwargs.get('libvirt_stream_protocols', [])
--
1.9.3
10 years
[v6] UI: Clone Guest(with precheck)
by huoyuxin@linux.vnet.ibm.com
From: Yu Xin Huo <huoyuxin(a)linux.vnet.ibm.com>
Signed-off-by: Yu Xin Huo <huoyuxin(a)linux.vnet.ibm.com>
---
ui/css/theme-default/button.css | 4 +
ui/css/theme-default/list.css | 18 +++++
ui/js/src/kimchi.api.js | 34 ++++++++-
ui/js/src/kimchi.guest_main.js | 151 +++++++++++++++++++++++++++++++++------
ui/pages/guest.html.tmpl | 6 ++-
ui/pages/i18n.json.tmpl | 1 +
6 files changed, 188 insertions(+), 26 deletions(-)
diff --git a/ui/css/theme-default/button.css b/ui/css/theme-default/button.css
index 499bf4a..0df53a6 100644
--- a/ui/css/theme-default/button.css
+++ b/ui/css/theme-default/button.css
@@ -50,6 +50,10 @@
cursor: pointer;
}
+.btn[disabled] {
+ opacity: 0.3;
+}
+
.btn:not([disabled]):hover {
box-shadow: -2px -2px 2px #dadada, 2px 2px 2px #fff, 3px 3px 3px white
inset, -3px -3px 3px rgba(0, 0, 0, .25) inset;
diff --git a/ui/css/theme-default/list.css b/ui/css/theme-default/list.css
index 8ffee69..ded18eb 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/kimchi-loading15x15.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..465755c 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;
@@ -806,12 +806,27 @@ var kimchi = {
});
},
- getStoragePoolVolume: function(poolName, volumeName, suc, err) {
+ getStoragePool: function(poolName, suc, err, async) {
+ var url = kimchi.url + 'storagepools/' + encodeURIComponent(poolName);
+ kimchi.requestJSON({
+ url : url,
+ type : 'GET',
+ contentType : 'application/json',
+ async : async==undefined?true:async,
+ timeout: 2000,
+ dataType : 'json',
+ success : suc,
+ error : err
+ });
+ },
+
+ getStoragePoolVolume: function(poolName, volumeName, suc, err, async) {
var url = kimchi.url + 'storagepools/' + encodeURIComponent(poolName) + '/storagevolumes/' + encodeURIComponent(volumeName);
kimchi.requestJSON({
url : url,
type : 'GET',
contentType : 'application/json',
+ async : async==undefined?true:async,
timeout: 2000,
dataType : 'json',
success : suc,
@@ -1233,5 +1248,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..b13903c 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();
@@ -276,21 +323,81 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) {
}
//Setup action event handlers
- guestActions.find("[name=vm-start]").on({click : kimchi.vmstart});
- guestActions.find("[name=vm-poweroff]").on({click : kimchi.vmpoweroff});
- if (vmRunningBool) { //If the guest is not running, do not enable reset
- guestActions.find("[name=vm-reset]").on({click : kimchi.vmreset});
- }
- if (vmRunningBool) { //If the guest is not running, do not enable shutdown
- guestActions.find("[name=vm-shutdown]").on({click : kimchi.vmshutdown});
- }
- guestActions.find("[name=vm-edit]").on({click : kimchi.vmedit});
- guestActions.find("[name=vm-delete]").on({click : kimchi.vmdelete});
+ if(!vmObject.isCloning){
+ guestActions.find("[name=vm-start]").on({click : kimchi.vmstart});
+ guestActions.find("[name=vm-poweroff]").on({click : kimchi.vmpoweroff});
+ if (vmRunningBool) { //If the guest is not running, do not enable reset
+ guestActions.find("[name=vm-reset]").on({click : kimchi.vmreset});
+ }
+ if (vmRunningBool) { //If the guest is not running, do not enable shutdown
+ guestActions.find("[name=vm-shutdown]").on({click : kimchi.vmshutdown});
+ }
+ 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(){
+ var guest = $(this).closest('li[name=guest]').attr("id");
+ kimchi.listVMStorages({
+ vm: guest,
+ storageType: 'disk'
+ }, function(disks){
+ var guestStorage = {};
+ for(var i=0; i<disks.length; i++){
+ if(disks[i].pool && disks[i].pool!=""){
+ var pool, volume;
+ kimchi.getStoragePool(disks[i].pool, function(data){
+ pool = data;
+ }, undefined, false);
+ kimchi.getStoragePoolVolume(disks[i].pool, disks[i].vol, function(data){
+ volume = data;
+ }, undefined, false);
+ if(!guestStorage[pool.name]){
+ guestStorage[pool.name] = {
+ pool: pool,
+ volumes: [volume],
+ totalVolumeSize: volume.capacity
+ };
+ }else{
+ guestStorage[pool.name].volumes.push(volume);
+ guestStorage[pool.name].totalVolumeSize += volume.capacity;
+ }
+ }
+ }
+ var needConfirm = false;
+ for(x in guestStorage){
+ if(guestStorage[x].pool.type.indexOf('scsi')!=-1||guestStorage[x].pool.available<guestStorage[x].totalVolumeSize){
+ needConfirm = true;
+ break;
+ }
+ }
+ if(needConfirm){
+ kimchi.confirm({
+ title : i18n['KCHAPI6006M'],
+ content : i18n['KCHVM6010M'],
+ confirm : i18n['KCHAPI6002M'],
+ cancel : i18n['KCHAPI6003M']
+ }, function() {
+ kimchi.cloneGuest(guest, function(data){
+ kimchi.listVmsAuto();
+ });
+ }, null);
+ }else{
+ kimchi.cloneGuest(guest, function(data){
+ kimchi.listVmsAuto();
+ });
+ }
+ });
+ });
+
+ //Maintain menu open state
+ var actionMenu=guestActions.find("div[name=actionmenu]");
+ if (openMenu) {
+ $('.popover', actionMenu).toggle();
+ }
- //Maintain menu open state
- var actionMenu=guestActions.find("div[name=actionmenu]");
- if (openMenu) {
- $('.popover', actionMenu).toggle();
+ }else{
+ guestActions.find('.btn').attr('disabled', true);
+ result.find('.guest-clone').removeClass('hide-content');
+ $('.popover', guestActions.find("div[name=actionmenu]")).remove();
}
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>
diff --git a/ui/pages/i18n.json.tmpl b/ui/pages/i18n.json.tmpl
index 75f1e43..56abd36 100644
--- a/ui/pages/i18n.json.tmpl
+++ b/ui/pages/i18n.json.tmpl
@@ -128,6 +128,7 @@
"KCHVM6007M": "$_("Note the guest OS may ignore this request. Would you like to continue?")",
"KCHVM6008M": "$_("Virtual Machine delete Confirmation")",
"KCHVM6009M": "$_("This virtual machine is not persistent. Power Off will delete it. Continue?")",
+ "KCHVM6010M": "$_("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?")",
"KCHVMCD6001M": "$_("This CDROM will be detached permanently and you can re-attach it. Continue to detach it?")",
"KCHVMCD6002M": "$_("Attach")",
--
1.7.1
10 years
[PATCH v1] Auto Topology for Guest Templates
by Christy Perez
This patchset is not completely tested, but I'd like feedback on broad
concepts, and also to share my methodology with the UI team.
If you read the comments (which I'lld delete in the final version),
there are some suggestions for how I intend this to interoperate with
the UI.
In the future, I'd also like to combine the rest of the CPU info
scattered across at least the host class and use this new CPUInfoModel
instead. But for now, I'd like to focus on getting the backend logic
working so that the user can create a template that has an auto-created
topology.
Christy Perez (1):
Parts to allow Kimchi to configure the cpu topology.
docs/API.md | 2 +
src/kimchi/API.json | 6 ++
src/kimchi/control/cpuinfo.py | 37 +++++++++
src/kimchi/i18n.py | 2 +
src/kimchi/model/cpuinfo.py | 176 ++++++++++++++++++++++++++++++++++++++++++
src/kimchi/model/host.py | 1 +
src/kimchi/model/templates.py | 13 +++-
7 files changed, 236 insertions(+), 1 deletion(-)
create mode 100644 src/kimchi/control/cpuinfo.py
create mode 100644 src/kimchi/model/cpuinfo.py
--
1.9.3
10 years
[PATCH 0/8] Clone virtual machines
by Crístian Viana
Crístian Viana (8):
Make function "randomMAC" public
Allow updating XML attribute in "xml_item_update"
Render different types of data in generate_action_handler
Add model function to wait for task
Clean up test pool directories
Create storage volume based on an existing volume
Clone virtual machines
Add tests and mockmodel for the cloning feature
docs/API.md | 8 +
src/kimchi/API.json | 5 +
src/kimchi/control/base.py | 37 +++--
src/kimchi/control/host.py | 18 +--
src/kimchi/control/vms.py | 1 +
src/kimchi/i18n.py | 4 +
src/kimchi/mockmodel.py | 102 ++++++++++++-
src/kimchi/model/storagevolumes.py | 73 ++++++++-
src/kimchi/model/tasks.py | 28 ++++
src/kimchi/model/vmifaces.py | 18 +--
src/kimchi/model/vms.py | 297 ++++++++++++++++++++++++++++++++++++-
src/kimchi/xmlutils/utils.py | 7 +-
tests/test_model.py | 83 +++++++++--
tests/test_rest.py | 54 +++++++
14 files changed, 682 insertions(+), 53 deletions(-)
--
1.9.3
10 years
[v1 0/1]Migrate virtual machines
by Simon Jin
This patch maybe bugy, only send out to get your early comments
Simon Jin (1):
Migrate virtual machines
docs/API.md | 3 +-
src/kimchi/control/vms.py | 1 +
src/kimchi/exception.py | 3 +
src/kimchi/i18n.py | 11 ++++
src/kimchi/model/vms.py | 138 ++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 155 insertions(+), 1 deletion(-)
--
1.8.3.1
10 years
Why Disable upload option
by ssdxiao
Hi kimchier,
I am happy to see the feature "upload volume" is supported in kimchi
but I found the button is disabled, is there any problem?
Now kimchi only supports nginx by default now.
Is there any plan to support other web server? For example: lighttpd, apache and so on.
10 years
[PATCH V3] Guest disk hot plug UI
by Wen Wang
From: Wen Wang <wenwang(a)linux.vnet.ibm.com>
V2 -> V3:
Minor changes according to JQuery rules.
V1 -> V2:
Enable CDROM hot plug including detaching.
Enable users add and detach disk when vm is running.
Signed-off-by: Wen Wang <wenwang(a)linux.vnet.ibm.com>
---
ui/js/src/kimchi.guest_edit_main.js | 29 +++++++++++------------------
1 files changed, 11 insertions(+), 18 deletions(-)
diff --git a/ui/js/src/kimchi.guest_edit_main.js b/ui/js/src/kimchi.guest_edit_main.js
index 030e112..fba7f05 100644
--- a/ui/js/src/kimchi.guest_edit_main.js
+++ b/ui/js/src/kimchi.guest_edit_main.js
@@ -57,17 +57,12 @@ kimchi.guest_edit_main = function() {
text: false
});
- if(kimchi.thisVMState != "running") {
- $('.detach', container).button({
- icons: {
- primary: 'ui-icon-trash'
- },
- text: false
- });
-
- } else {
- $('.detach', container).remove();
- }
+ $('.detach', container).button({
+ icons: {
+ primary: 'ui-icon-trash'
+ },
+ text: false
+ });
$('.save', container).button({
icons: {
@@ -443,14 +438,8 @@ kimchi.guest_edit_main = function() {
guest['icon'] = guest['icon'] || 'images/icon-vm.png';
$('#form-guest-edit-general').fillWithObject(guest);
kimchi.thisVMState = guest['state'];
-
refreshCDROMs();
- if(kimchi.thisVMState === "running") {
- $("#form-guest-edit-general input").prop("disabled", "disabled");
- $("#guest-edit-attach-cdrom-button").remove();
- $("#form-guest-edit-interface .header button").remove();
- } else {
- $('#guest-edit-attach-cdrom-button').button({
+ $('#guest-edit-attach-cdrom-button').button({
icons: {
primary: "ui-icon-plusthick"
},
@@ -459,6 +448,10 @@ kimchi.guest_edit_main = function() {
event.preventDefault();
kimchi.window.open("guest-storage-add.html");
});
+ if(kimchi.thisVMState === "running") {
+ $("#form-guest-edit-general input").prop("disabled", true);
+ $("#form-guest-edit-interface .header button").remove();
+ } else {
$("#action-button-container").removeClass("hidden");
}
--
1.7.1
10 years
[PATCH V4] Guest disk hot plug UI
by Wen Wang
From: Wen Wang <wenwang(a)linux.vnet.ibm.com>
V3 -> v4:
Disable CDROM hotplug due to back-end limitatiions
V2 -> V3:
Minor changes according to JQuery rules.
V1 -> V2:
Enable CDROM hot plug including detaching.
Enable users add and detach disk when vm is running.
Signed-off-by: Wen Wang <wenwang(a)linux.vnet.ibm.com>
---
ui/js/src/kimchi.guest_edit_main.js | 30 ++++++++++++---------------
ui/js/src/kimchi.guest_storage_add.main.js | 18 +++++++++++++---
2 files changed, 27 insertions(+), 21 deletions(-)
diff --git a/ui/js/src/kimchi.guest_edit_main.js b/ui/js/src/kimchi.guest_edit_main.js
index ed8c689..9d87a73 100644
--- a/ui/js/src/kimchi.guest_edit_main.js
+++ b/ui/js/src/kimchi.guest_edit_main.js
@@ -57,16 +57,14 @@ kimchi.guest_edit_main = function() {
text: false
});
- if(kimchi.thisVMState != "running") {
- $('.detach', container).button({
- icons: {
- primary: 'ui-icon-trash'
- },
- text: false
- });
-
- } else {
- $('.detach', container).remove();
+ $('.detach', container).button({
+ icons: {
+ primary: 'ui-icon-trash'
+ },
+ text: false
+ });
+ if (kimchi.thisVMState === 'running') {
+ $('.detach[data-type="cdrom"]', container).remove();
}
$('.save', container).button({
@@ -467,14 +465,8 @@ kimchi.guest_edit_main = function() {
guest['icon'] = guest['icon'] || 'images/icon-vm.png';
$('#form-guest-edit-general').fillWithObject(guest);
kimchi.thisVMState = guest['state'];
-
refreshCDROMs();
- if(kimchi.thisVMState === "running") {
- $("#form-guest-edit-general input").prop("disabled", "disabled");
- $("#guest-edit-attach-cdrom-button").remove();
- $("#form-guest-edit-interface .header button").remove();
- } else {
- $('#guest-edit-attach-cdrom-button').button({
+ $('#guest-edit-attach-cdrom-button').button({
icons: {
primary: "ui-icon-plusthick"
},
@@ -483,6 +475,10 @@ kimchi.guest_edit_main = function() {
event.preventDefault();
kimchi.window.open("guest-storage-add.html");
});
+ if(kimchi.thisVMState === "running") {
+ $("#form-guest-edit-general input").prop("disabled", true);
+ $("#form-guest-edit-interface .header button").remove();
+ } else {
$("#action-button-container").removeClass("hidden");
}
diff --git a/ui/js/src/kimchi.guest_storage_add.main.js b/ui/js/src/kimchi.guest_storage_add.main.js
index 57eed6a..5df5625 100644
--- a/ui/js/src/kimchi.guest_storage_add.main.js
+++ b/ui/js/src/kimchi.guest_storage_add.main.js
@@ -24,7 +24,10 @@ kimchi.guest_storage_add_main = function() {
label: 'disk',
value: 'disk',
}];
- kimchi.select('guest-storage-type-list', types);
+ var typesRunning = [{
+ label: 'disk',
+ value: 'disk'
+ }];
var storageAddForm = $('#form-guest-storage-add');
var submitButton = $('#guest-storage-button-add');
@@ -32,10 +35,8 @@ kimchi.guest_storage_add_main = function() {
var pathTextbox = $('input[name="path"]', storageAddForm);
var poolTextbox = $('input[name="pool"]', storageAddForm);
var volTextbox = $('input[name="vol"]', storageAddForm);
- var selectType = $(typeTextbox).val();
typeTextbox.change(function() {
- $('#guest-storage-bus').selectMenu();
var pathObject = {'cdrom': ".path-section", 'disk': '.volume-section'}
selectType = $(this).val();
$.each(pathObject, function(type, value) {
@@ -84,7 +85,7 @@ kimchi.guest_storage_add_main = function() {
options.push({
label: value.name,
value: value.name
- });
+ });
}
});
if (options.length) {
@@ -109,6 +110,15 @@ kimchi.guest_storage_add_main = function() {
});
});
+ if (kimchi.thisVMState === 'running') {
+ types =typesRunning;
+ $(typeTextbox).val('disk');
+ typeTextbox.change();
+ poolTextbox.change();
+ }
+ var selectType = $(typeTextbox).val();
+ kimchi.select('guest-storage-type-list', types);
+
var validateCDROM = function(settings) {
if (/^((https|http|ftp|ftps|tftp|\/).*)+$/.test(settings['path']))
return true;
--
1.7.1
10 years
[v4] UI: Clone Guest
by huoyuxin@linux.vnet.ibm.com
From: Yu Xin Huo <huoyuxin(a)linux.vnet.ibm.com>
Signed-off-by: Yu Xin Huo <huoyuxin(a)linux.vnet.ibm.com>
---
ui/css/theme-default/button.css | 4 ++
ui/css/theme-default/list.css | 18 ++++++
ui/js/src/kimchi.api.js | 17 +++++-
ui/js/src/kimchi.guest_main.js | 111 +++++++++++++++++++++++++++++++--------
ui/pages/guest.html.tmpl | 6 ++-
ui/pages/i18n.json.tmpl | 1 +
6 files changed, 132 insertions(+), 25 deletions(-)
diff --git a/ui/css/theme-default/button.css b/ui/css/theme-default/button.css
index 499bf4a..0df53a6 100644
--- a/ui/css/theme-default/button.css
+++ b/ui/css/theme-default/button.css
@@ -50,6 +50,10 @@
cursor: pointer;
}
+.btn[disabled] {
+ opacity: 0.3;
+}
+
.btn:not([disabled]):hover {
box-shadow: -2px -2px 2px #dadada, 2px 2px 2px #fff, 3px 3px 3px white
inset, -3px -3px 3px rgba(0, 0, 0, .25) inset;
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..2fd5c55 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();
@@ -276,21 +323,41 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) {
}
//Setup action event handlers
- guestActions.find("[name=vm-start]").on({click : kimchi.vmstart});
- guestActions.find("[name=vm-poweroff]").on({click : kimchi.vmpoweroff});
- if (vmRunningBool) { //If the guest is not running, do not enable reset
- guestActions.find("[name=vm-reset]").on({click : kimchi.vmreset});
- }
- if (vmRunningBool) { //If the guest is not running, do not enable shutdown
- guestActions.find("[name=vm-shutdown]").on({click : kimchi.vmshutdown});
- }
- guestActions.find("[name=vm-edit]").on({click : kimchi.vmedit});
- guestActions.find("[name=vm-delete]").on({click : kimchi.vmdelete});
+ if(!vmObject.isCloning){
+ guestActions.find("[name=vm-start]").on({click : kimchi.vmstart});
+ guestActions.find("[name=vm-poweroff]").on({click : kimchi.vmpoweroff});
+ if (vmRunningBool) { //If the guest is not running, do not enable reset
+ guestActions.find("[name=vm-reset]").on({click : kimchi.vmreset});
+ }
+ if (vmRunningBool) { //If the guest is not running, do not enable shutdown
+ guestActions.find("[name=vm-shutdown]").on({click : kimchi.vmshutdown});
+ }
+ 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(){
+ var guest = $(this).closest('li[name=guest]').attr("id");
+ kimchi.confirm({
+ title : i18n['KCHAPI6006M'],
+ content : i18n['KCHVM6010M'],
+ confirm : i18n['KCHAPI6002M'],
+ cancel : i18n['KCHAPI6003M']
+ }, function() {
+ kimchi.cloneGuest(guest, function(data){
+ kimchi.listVmsAuto();
+ });
+ }, null);
+ });
+
+ //Maintain menu open state
+ var actionMenu=guestActions.find("div[name=actionmenu]");
+ if (openMenu) {
+ $('.popover', actionMenu).toggle();
+ }
- //Maintain menu open state
- var actionMenu=guestActions.find("div[name=actionmenu]");
- if (openMenu) {
- $('.popover', actionMenu).toggle();
+ }else{
+ guestActions.find('.btn').attr('disabled', true);
+ result.find('.guest-clone').removeClass('hide-content');
+ $('.popover', guestActions.find("div[name=actionmenu]")).remove();
}
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>
diff --git a/ui/pages/i18n.json.tmpl b/ui/pages/i18n.json.tmpl
index 75f1e43..56abd36 100644
--- a/ui/pages/i18n.json.tmpl
+++ b/ui/pages/i18n.json.tmpl
@@ -128,6 +128,7 @@
"KCHVM6007M": "$_("Note the guest OS may ignore this request. Would you like to continue?")",
"KCHVM6008M": "$_("Virtual Machine delete Confirmation")",
"KCHVM6009M": "$_("This virtual machine is not persistent. Power Off will delete it. Continue?")",
+ "KCHVM6010M": "$_("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?")",
"KCHVMCD6001M": "$_("This CDROM will be detached permanently and you can re-attach it. Continue to detach it?")",
"KCHVMCD6002M": "$_("Attach")",
--
1.7.1
10 years
[v5] UI: Clone Guest(with precheck)
by huoyuxin@linux.vnet.ibm.com
From: Yu Xin Huo <huoyuxin(a)linux.vnet.ibm.com>
Signed-off-by: Yu Xin Huo <huoyuxin(a)linux.vnet.ibm.com>
---
ui/css/theme-default/button.css | 4 +
ui/css/theme-default/list.css | 18 +++++
ui/js/src/kimchi.api.js | 34 ++++++++-
ui/js/src/kimchi.guest_main.js | 149 +++++++++++++++++++++++++++++++++------
ui/pages/guest.html.tmpl | 6 ++-
ui/pages/i18n.json.tmpl | 1 +
6 files changed, 186 insertions(+), 26 deletions(-)
diff --git a/ui/css/theme-default/button.css b/ui/css/theme-default/button.css
index 499bf4a..0df53a6 100644
--- a/ui/css/theme-default/button.css
+++ b/ui/css/theme-default/button.css
@@ -50,6 +50,10 @@
cursor: pointer;
}
+.btn[disabled] {
+ opacity: 0.3;
+}
+
.btn:not([disabled]):hover {
box-shadow: -2px -2px 2px #dadada, 2px 2px 2px #fff, 3px 3px 3px white
inset, -3px -3px 3px rgba(0, 0, 0, .25) inset;
diff --git a/ui/css/theme-default/list.css b/ui/css/theme-default/list.css
index 8ffee69..ded18eb 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/kimchi-loading15x15.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..465755c 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;
@@ -806,12 +806,27 @@ var kimchi = {
});
},
- getStoragePoolVolume: function(poolName, volumeName, suc, err) {
+ getStoragePool: function(poolName, suc, err, async) {
+ var url = kimchi.url + 'storagepools/' + encodeURIComponent(poolName);
+ kimchi.requestJSON({
+ url : url,
+ type : 'GET',
+ contentType : 'application/json',
+ async : async==undefined?true:async,
+ timeout: 2000,
+ dataType : 'json',
+ success : suc,
+ error : err
+ });
+ },
+
+ getStoragePoolVolume: function(poolName, volumeName, suc, err, async) {
var url = kimchi.url + 'storagepools/' + encodeURIComponent(poolName) + '/storagevolumes/' + encodeURIComponent(volumeName);
kimchi.requestJSON({
url : url,
type : 'GET',
contentType : 'application/json',
+ async : async==undefined?true:async,
timeout: 2000,
dataType : 'json',
success : suc,
@@ -1233,5 +1248,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..48e71f7 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();
@@ -276,21 +323,79 @@ kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) {
}
//Setup action event handlers
- guestActions.find("[name=vm-start]").on({click : kimchi.vmstart});
- guestActions.find("[name=vm-poweroff]").on({click : kimchi.vmpoweroff});
- if (vmRunningBool) { //If the guest is not running, do not enable reset
- guestActions.find("[name=vm-reset]").on({click : kimchi.vmreset});
- }
- if (vmRunningBool) { //If the guest is not running, do not enable shutdown
- guestActions.find("[name=vm-shutdown]").on({click : kimchi.vmshutdown});
- }
- guestActions.find("[name=vm-edit]").on({click : kimchi.vmedit});
- guestActions.find("[name=vm-delete]").on({click : kimchi.vmdelete});
+ if(!vmObject.isCloning){
+ guestActions.find("[name=vm-start]").on({click : kimchi.vmstart});
+ guestActions.find("[name=vm-poweroff]").on({click : kimchi.vmpoweroff});
+ if (vmRunningBool) { //If the guest is not running, do not enable reset
+ guestActions.find("[name=vm-reset]").on({click : kimchi.vmreset});
+ }
+ if (vmRunningBool) { //If the guest is not running, do not enable shutdown
+ guestActions.find("[name=vm-shutdown]").on({click : kimchi.vmshutdown});
+ }
+ 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(){
+ var guest = $(this).closest('li[name=guest]').attr("id");
+ kimchi.listVMStorages({
+ vm: guest,
+ storageType: 'disk'
+ }, function(disks){
+ var guestStorage = {};
+ for(var i=0; i<disks.length; i++){
+ var pool, volume;
+ kimchi.getStoragePool(disks[i].pool, function(data){
+ pool = data;
+ }, undefined, false);
+ kimchi.getStoragePoolVolume(disks[i].pool, disks[i].vol, function(data){
+ volume = data;
+ }, undefined, false);
+ if(!guestStorage[pool.name]){
+ guestStorage[pool.name] = {
+ pool: pool,
+ volumes: [volume],
+ totalVolumeSize: volume.capacity
+ };
+ }else{
+ guestStorage[pool.name].volumes.push(volume);
+ guestStorage[pool.name].totalVolumeSize += volume.capacity;
+ }
+ }
+ var needConfirm = false;
+ for(x in guestStorage){
+ if(guestStorage[x].pool.type.indexOf('scsi')!=-1||guestStorage[x].pool.available<guestStorage[x].totalVolumeSize){
+ needConfirm = true;
+ break;
+ }
+ }
+ if(needConfirm){
+ kimchi.confirm({
+ title : i18n['KCHAPI6006M'],
+ content : i18n['KCHVM6010M'],
+ confirm : i18n['KCHAPI6002M'],
+ cancel : i18n['KCHAPI6003M']
+ }, function() {
+ kimchi.cloneGuest(guest, function(data){
+ kimchi.listVmsAuto();
+ });
+ }, null);
+ }else{
+ kimchi.cloneGuest(guest, function(data){
+ kimchi.listVmsAuto();
+ });
+ }
+ });
+ });
+
+ //Maintain menu open state
+ var actionMenu=guestActions.find("div[name=actionmenu]");
+ if (openMenu) {
+ $('.popover', actionMenu).toggle();
+ }
- //Maintain menu open state
- var actionMenu=guestActions.find("div[name=actionmenu]");
- if (openMenu) {
- $('.popover', actionMenu).toggle();
+ }else{
+ guestActions.find('.btn').attr('disabled', true);
+ result.find('.guest-clone').removeClass('hide-content');
+ $('.popover', guestActions.find("div[name=actionmenu]")).remove();
}
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>
diff --git a/ui/pages/i18n.json.tmpl b/ui/pages/i18n.json.tmpl
index 75f1e43..56abd36 100644
--- a/ui/pages/i18n.json.tmpl
+++ b/ui/pages/i18n.json.tmpl
@@ -128,6 +128,7 @@
"KCHVM6007M": "$_("Note the guest OS may ignore this request. Would you like to continue?")",
"KCHVM6008M": "$_("Virtual Machine delete Confirmation")",
"KCHVM6009M": "$_("This virtual machine is not persistent. Power Off will delete it. Continue?")",
+ "KCHVM6010M": "$_("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?")",
"KCHVMCD6001M": "$_("This CDROM will be detached permanently and you can re-attach it. Continue to detach it?")",
"KCHVMCD6002M": "$_("Attach")",
--
1.7.1
10 years