[v5] UI: Clone Guest(static message)
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..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..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
[PATCH] 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
Hot plug tests fails for scsi-cdrom
by Royce Lv
After finished patch for scsi cdrom and passed tests against cold
plug/vm creation, when I were testing hot plug for cdrom, I got the
following error:
libvirt: QEMU Driver error : internal error: No device with bus
'scsi' and target 'sda'
When debugging and searching, found this reply in libvirt community:
https://www.redhat.com/archives/libvirt-users/2013-December/msg00033.html
https://www.redhat.com/archives/libvirt-users/2013-December/msg00034.html
Quote from libvirt maintainer Daniel B that:
"
There is no way to hotplug a CDROM device - only disk devices can be
hotplugged.
For CDROM/Floppy devices it is looking for an existing device with the
given name, and then trying to change the removable media in that device.
The error you're getting shows that there is no such CDROM device with the
name 'vda' in the guest already.
"
So I think we need to abandon this scsi cdrom patch and just use current
ide cdrom.
And in UI part, we also need to disable hot plug for cdrom.
10 years
[v3] 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 | 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..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..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
[WIP PATCH 0/5] Clone VMs
by Crístian Viana
To clone a VM, try the following command:
POST /vms/<vm-name>/clone.
This is a work in progress! It's not finished yet and there are bugs! I'm only
sending it now to get early feedback.
I'm aware of the following issues:
- BUG: cannot delete a recently cloned VM. This is because the clone function
doesn't refresh the VM storage pools. I already started fixing this.
- The storage volumes handling is not implemented. The current implementation
should only work for DIR and NFS (well, it doesn't work completely because of
the bug described above...).
- The mockmodel implementation is missing.
- The tests are missing.
Crístian Viana (5):
Make function "randomMAC" public
Register Kimchi's namespace when updating XML
Allow to update XML attribute in "xml_item_update"
Render different types of data in generate_action_handler
Add feature to clone VMs
docs/API.md | 5 ++
src/kimchi/control/base.py | 37 ++++++++++----
src/kimchi/control/host.py | 18 ++-----
src/kimchi/control/vms.py | 1 +
src/kimchi/i18n.py | 1 +
src/kimchi/model/vmifaces.py | 18 +++----
src/kimchi/model/vms.py | 116 ++++++++++++++++++++++++++++++++++++++++++-
src/kimchi/xmlutils.py | 15 ++++--
8 files changed, 172 insertions(+), 39 deletions(-)
--
1.9.3
10 years
RFC: Domain snapshots (backend)
by Crístian Viana
Hi everyone,
I'm presenting here my proposal for the feature "Domain snapshots" which
is expected to be implemented for Kimchi 1.4.
Description
A domain snapshot is a "moment in time" of a domain. After a snapshot is
created, a domain can be reverted to that snapshot (i.e. reverted to the
exact same state it was when the snapshot was created). If a snapshot is
created while the domain is stopped, only its disks will be used in that
snapshot; otherwise, its memory state will also be used.
A snapshot can be created on top of another snapshot, which makes it
possible to have a tree-like snapshot structure for each domain. When a
snapshot is created, the last snapshot used in that domain execution
will be its parent. If there are no previous snapshot in that execution,
the new snapshot will have no parent.
Existing functions which also deal with snapshots will be updated.
REST API
Create a domain snapshot
Syntax
POST /vms//<vm-name>//snapshots
Parameters
name: The snapshot name (optional). If omitted, a default name will be used.
Return
An asynchronous Task with "target_uri" containing
"/vms/</vm-name/>/snapshots//<new-snapshot-name>/". As expected with any
Task, the process can be tracked by checking the corresponding task's
status.
Look up one domain snapshot
Syntax
GET /vms//<vm-name>//snapshots//<snapshot-name>/
Parameters
None.
Return
A snapshot structure with the following properties:
name: The snapshot name.
state: The corresponding domain state when the snapshot was created.
created: The time when the snapshot was created (in seconds, since the
epoch).
parent: The name of the parent snapshot, or an empty string if there is
no parent.
Look up multiple domain snapshosts
Syntax
GET /vms//<vm-name>//snapshots
Parameters
parent: The name of the parent snapshot of the requested snapshots
(optional). If omitted, only the "root" snapshots for that domain will
be returned.
Return
A list of the structures returned by the action "Look up one domain
snapshot". All snapshots returned by this call will have "parent" as
their parents.
Update a domain snapshot
Syntax
PUT /vms//<vm-name>//snapshots//<snapshot-name>/
Parameters:
name: The new snapshot name.
Return:
The structure returned by the action "Look up one domain snapshot" with
the updated values.
Delete a domain snapshot
Syntax
DELETE /vms//<vm-name>//snapshots//<snapshot-name>/
Parameters
None.
Return
None.
Revert to a domain snapshot
Syntax
POST /vms//<vm-name>//snapshots//<snapshot-name>//revert
Parameters
None.
Return
None.
CHANGE: List the current snapshot in a domain
Syntax
GET /vms//<vm-name>/
Additional parameters
None.
Additional return
current-snapshot: the snapshot name in which the domain execution is on,
or an empty string if there is no current snapshot.
CHANGE: Delete snapshots when deleting a domain
Syntax
DELETE /vms//<vm-name>/
Additional parameters
None.
Additional return
None.
CHANGE: Clone snapshots when cloning a domain
Syntax
POST /vms//<vm-name>//clone
Additional parameters
None.
Additional return
None.
Open discussion
1. When creating a snapshot in a running domain, should we try to
minimize the domain's downtime? There's a libvirt flag for that but
it increases the memory dump file size.
2. When deleting a snapshot, should we keep their children or should we
only delete that snapshot? Keep in mind that snapshots follow a
tree-like structure. This feature is already implemented by libvirt,
it makes no difference for us in the implementation (it's just a
different flag), but we need to think if the user would expect that
the snapshot's children will be deleted as well when deleting one
snapshot.
3. The properties returned by the action "Look up one domain snapshot"
are just the ones I thought it might be useful for now. A snapshot
has more properties than that but I'm not sure if we should just
dump everything.
4. The property updated by the action "Update a domain snapshot" (i.e.
"name") is the only one I thought it might be relevant. Other
properties can be updated as well if we want to.
Any feedback is welcome.
Best regards,
Crístian.
10 years
[PATCH 0/7] Use the common get_disk_xml() to generate VMTemplate disks
by Aline Manera
Aline Manera (7):
Make disk type an optional parameter on get_disk_xml()
Set guest disk cache to none to support live migration
Change VMTemplate._get_scsi_disks_xml() to use the common
get_disk_xml()
Change VMTemplate._get_iscsi_disks_xml() to use the common
get_disk_xml()
Change VMTemplate._get_disks_xml() to use the common get_disk_xml()
Change VMTemplate._get_disks_xml() to handle all type of disks
Remove VMTemplate._get_scsi_disks_xml() and
VMTemplate._get_iscsi_disks_xml()
src/kimchi/model/vmstorages.py | 1 +
src/kimchi/vmtemplate.py | 108 ++++++++++++-----------------------------
src/kimchi/xmlutils/disk.py | 10 +++-
tests/test_model.py | 3 +-
4 files changed, 42 insertions(+), 80 deletions(-)
--
1.9.3
10 years
[PATCHv2] ISSUE#466: Filter valid format only on volume type of 'file'
by lvroyce0210@gmail.com
From: Royce Lv <lvroyce(a)linux.vnet.ibm.com>
When volume type is block,
filter will prevent volume to be attached due to its format is 'unknown'.
Change the filter to only apply to 'file' type,
so that block will not be blocked.
Signed-off-by: Royce Lv <lvroyce(a)linux.vnet.ibm.com>
---
src/kimchi/model/vmstorages.py | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/src/kimchi/model/vmstorages.py b/src/kimchi/model/vmstorages.py
index 055aa50..fea110f 100644
--- a/src/kimchi/model/vmstorages.py
+++ b/src/kimchi/model/vmstorages.py
@@ -153,17 +153,18 @@ class VMStoragesModel(object):
if vol_info['ref_cnt'] != 0:
raise InvalidParameter("KCHVMSTOR0016E")
- supported_format = {
+ params['path'] = vol_info['path']
+ valid_format = {
"disk": ["raw", "bochs", "qcow", "qcow2", "qed", "vmdk"],
"cdrom": "iso"}
- if vol_info['format'] in supported_format[params['type']]:
- if params['type'] == 'disk':
+ if vol_info['type'] == 'file':
+ if (params['type'] == 'disk' and
+ vol_info['format'] in valid_format[params['type']]):
params['format'] = vol_info['format']
- else:
- raise InvalidParameter("KCHVMSTOR0018E",
- {"format": vol_info['format'],
- "type": params['type']})
- params['path'] = vol_info['path']
+ else:
+ raise InvalidParameter("KCHVMSTOR0018E",
+ {"format": vol_info['format'],
+ "type": params['type']})
params['src_type'] = _check_path(params['path'])
if (params['bus'] not in HOTPLUG_TYPE
and DOM_STATE_MAP[dom.info()[0]] != 'shutoff'):
--
1.8.3.2
10 years
[PATCH] Edit Template redefined
by Wen Wang
From: Wen Wang <wenwang(a)linux.vnet.ibm.com>
This patch redesigned "Edit Template" diaguage in "Templates". New "Edit
Template" will display all the related information into tabs of
"General", "Storage" and "Interface". Due to unfinished back-end work,
functions are not fully supported, which will be finished in the future
work.
Temporary disabled functions:
1) Multiple disk operation with multiple storage pools edit in template
for which reason the add button in "Storage" tab is disabled.
2) iSCSI and SCSI storage pool add is removed since we are going to
allow this kind of operation in the process of creating a VM.
Signed-off-by: Wen Wang <wenwang(a)linux.vnet.ibm.com>
---
ui/css/theme-default/template-edit.css | 84 +++++++-
ui/js/src/kimchi.template_edit_main.js | 336 ++++++++++++++++++++++----------
ui/pages/template-edit.html.tmpl | 169 +++++++++-------
3 files changed, 402 insertions(+), 187 deletions(-)
diff --git a/ui/css/theme-default/template-edit.css b/ui/css/theme-default/template-edit.css
index 4975f1b..302c488 100644
--- a/ui/css/theme-default/template-edit.css
+++ b/ui/css/theme-default/template-edit.css
@@ -17,24 +17,33 @@
*/
#template-edit-window {
font-size: 13px;
- height: 600px;
- width: 1000px;
+ height: 500px;
+ width: 800px;
}
-.template-edit-fieldset {
- float: left;
- padding: 1em;
+#edit-template-tabs {
+ background: none repeat scroll 0 0 transparent;
+ border: medium none;
+ height: 100%;
+ padding: 0;
}
-.template-edit-wrapper-label, .template-edit-wrapper-controls {
+#edit-template-tabs .form-template-inline-wrapper {
+ display: inline-block;
vertical-align: top;
- width: 470px;
}
.template-edit-wrapper-label {
- height: 18px;
- line-height: 18px;
- margin-top: 8px;
+ vertical-align: top;
+ min-width: 100px;
+ height: 35px;
+ line-height: 35px;
+ margin: 7px 0 8px;
+}
+
+.template-edit-wrapper-controls {
+ vertical-align: top;
+ width: 400px;
}
.template-edit-wrapper-controls input[type="text"] {
@@ -56,7 +65,7 @@
.template-edit-wrapper-controls > .dropdown {
margin: 5px 0 0 1px;
- width: 440px;
+ width: 372px;
}
.template-edit-wrapper-controls input[type="text"][disabled] {
@@ -103,3 +112,56 @@ input[type="checkbox"]:CHECKED+.item {
background: #f8f8f8 url(../images/theme-default/check-green.png) no-repeat
10px center;
}
+
+#edit-template-tabs .template-tab-header {
+ margin-bottom: 8px;
+ padding-bottom: 2px;
+ font-weight: bold;
+ border-bottom: 1px solid #999999;
+ overflow: hidden;
+}
+
+#edit-template-tabs .template-tab-header .action-area {
+ float: right;
+ height: 20px;
+ width: 20px;
+}
+
+#edit-template-tabs .template-storage-cell{
+ display: inline-block;
+ width: 230px;
+}
+
+#edit-template-tabs .template-interface-cell {
+ display: inline-block;
+ width: 250px;
+}
+
+#form-template-storage .template-tab-body select {
+ width: 140px;
+}
+
+#form-template-storage .template-tab-body input {
+ width: 56px;
+}
+
+#edit-template-tabs .template-tab-body .item {
+ margin: 5px 0;
+}
+
+#form-template-interface .template-tab-body select {
+ width: 180px;
+}
+
+#edit-template-tabs .template-tab-body .action-area {
+ float: right;
+}
+
+#edit-template-tabs .template-tab-body .action-area button {
+ width: 20px;
+ height: 20px;
+}
+
+#edit-template-tabs .template-tab-body .hide {
+ display: none;
+}
\ No newline at end of file
diff --git a/ui/js/src/kimchi.template_edit_main.js b/ui/js/src/kimchi.template_edit_main.js
index 2f4cc9a..ac6cff8 100644
--- a/ui/js/src/kimchi.template_edit_main.js
+++ b/ui/js/src/kimchi.template_edit_main.js
@@ -16,14 +16,18 @@
* limitations under the License.
*/
kimchi.template_edit_main = function() {
- var templateEditForm = $('#form-template-edit');
+ var templateEditMain = $('#edit-template-tabs');
var origDisks;
var origPool;
+ var origNetworks;
var templateDiskSize;
- $('#template-name', templateEditForm).val(kimchi.selectedTemplate);
- kimchi.retrieveTemplate(kimchi.selectedTemplate, function(template) {
+ $('#template-name', templateEditMain).val(kimchi.selectedTemplate);
+ templateEditMain.tabs();
+
+ var initGeneral = function(template) {
origDisks = template.disks;
origPool = template.storagepool;
+ origNetworks = template.networks;
for(var i=0;i<template.disks.length;i++){
if(template.disks[i].base){
template["vm-image"] = template.disks[i].base;
@@ -37,18 +41,12 @@ kimchi.template_edit_main = function() {
if (prop == 'graphics') {
value = value["type"];
}
- $('input[name="' + prop + '"]', templateEditForm).val(value);
- }
- var disks = template.disks;
- $('input[name="disks"]').val(disks[0].size);
- templateDiskSize = $('input[name="disks"]').val();
- if (disks[0].volume) {
- var spool_value = $('#form-template-edit [name="storagepool"]').val();
- $('input[name="storagepool"]', templateEditForm).val(spool_value + '/' + disks[0].volume);
- $('input[name="disks"]', templateEditForm).attr('disabled','disabled');
+ $('input[name="' + prop + '"]', templateEditMain).val(value);
}
var vncOpt = [{label: 'VNC', value: 'vnc'}];
+ $('#template-edit-graphics').append('<option selected>VNC</option>');
+ $('#template-edit-graphics').append('<option>Spice</option>');
kimchi.select('template-edit-graphics-list', vncOpt);
var enableSpice = function() {
if (kimchi.capabilities == undefined) {
@@ -61,85 +59,215 @@ kimchi.template_edit_main = function() {
}
};
enableSpice();
-
+ };
+ var initStorage = function(result) {
var scsipools = {};
- kimchi.listStoragePools(function(result) {
- var options = [];
- if (result && result.length) {
- $.each(result, function(index, storagePool) {
- if ((storagePool.state=="active") && (storagePool.type !== 'kimchi-iso')) {
- if ((storagePool.type == 'iscsi') || (storagePool.type == 'scsi')){
- scsipools[storagePool.name] = [];
- kimchi.listStorageVolumes(storagePool.name, function(result) {
- if (result && result.length) {
- $.each(result, function(index, storageVolume) {
- options.push({
- label: storagePool.name + '/' + storageVolume.name,
- value: '/storagepools/' + storagePool.name + '/' + storageVolume.name
- });
- scsipools[storagePool.name].push(storageVolume)
- });
- }
- kimchi.select('template-edit-storagePool-list', options);
- });
- }
- else {
- options.push({
- label: storagePool.name,
- value: '/storagepools/' + storagePool.name
- });
- }
+ var addStorageItem = function(storageData) {
+ var nodeStorage = $.parseHTML(kimchi.substitute($("#template-storage-pool-tmpl").html(), storageData));
+ $('.template-tab-body', '#form-template-storage').append(nodeStorage);
+ $('.edit', '#form-template-storage').button({
+ icons : {primary : 'ui-icon-pencil'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ var storageItem = $(this).parent().parent();
+ $('.template-storage-name', storageItem).hide();
+ var currentName = $('.template-storage-name', storageItem).text();
+ $('.template-storage-disk', storageItem).hide();
+ var currentDisk = $('.template-storage-disk', storageItem).text();
+ $('input', storageItem).val(currentDisk).show();
+ $('select', storageItem).show();
+ if (currentName === 'iscsi' || currentName === 'scsi') {
+ $('input', storageItem).attr('disabled', true);
+ } else {
+ $('input', storageItem).attr('disabled', false);
+ }
+ $('.save', storageItem).parent().show();
+ $('.delete', storageItem).parent().hide();
+ var selectedStorage = $('select', storageItem).val();
+ $('.template-storage-name', storageItem).text(selectedStorage).hide();
+ $('select', storageItem).show();
+ });
+ $('.delete', '#form-template-storage').button({
+ icons : {primary : 'ui-icon-trash'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ $(this).parent().parent().remove();
+ });
+ $('.cancel', '#form-template-storage').button({
+ icons : {primary : 'ui-icon-arrowreturnthick-1-w'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ var cancelEntity = $(this).parent().parent();
+ if($('.template-storage-disk', cancelEntity).text() === '') {
+ cancelEntity.remove();
+ } else {
+ $('label', cancelEntity).show();
+ $('input', cancelEntity).hide();
+ $('select', cancelEntity).hide();
+ $('.cancel', cancelEntity).parent().hide();
+ $('.edit', cancelEntity).parent().show();
+ }
+ });
+ $('.save', '#form-template-storage').button({
+ icons : {primary : 'ui-icon-disk'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ var storageItem = $(this).parent().parent();
+ $('.save', storageItem).parent().hide();
+ $('.delete', storageItem).parent().show();
+ var selectedStorage = $('select', storageItem).val();
+ $('.template-storage-name', storageItem).text(selectedStorage).show();
+ $('.template-storage-disk', storageItem).text($('input', storageItem).val()).show();
+ $('select', storageItem).hide();
+ $('input', storageItem).hide();
+ });
+ var storageOptions = '';
+ $.each(result, function(index, storageEntities) {
+ if((storageEntities.state === 'active') && (storageEntities.type != 'kimchi-iso')
+ && (storageEntities.type != 'iscsi') && (storageEntities.type != 'scsi')){
+ var isSlected = storageEntities.name === 'default' ? ' selected' : '';
+ storageOptions += '<option' + isSlected + '>' + result[index].name + '</option>';
+ }
+ });
+ $('select', '#form-template-storage').find('option').remove();
+ $('select', '#form-template-storage').append(storageOptions);
+ $('select', '#form-template-storage').change(function() {
+ var selectedItem = $(this).parent().parent();
+ var tempStorageName = $(this).val();
+ if (tempStorageName === 'iscsi' || tempStorageName === 'scsi') {
+ $('input', selectedItem).attr('disabled', true);
+ } else {
+ $('input', selectedItem).attr('disabled', false);
+ }
+ $.each(result, function(index, storageEntities) {
+ if (tempStorageName === storageEntities.name) {
+ selectedItem.find('.template-storage-type').text(storageEntities.type);
}
- });
- }
- if ($.isEmptyObject(scsipools)) {
- kimchi.select('template-edit-storagePool-list', options);
+ })
+ });
+ };
+
+ if ((origDisks && origDisks.length) && (origPool && origPool)) {
+ splitPool = origPool.split('/');
+ var defaultPool;
+ var defaultType;
+ $.each(result, function(index, poolEntities) {
+ if (poolEntities.name === splitPool[splitPool.length-1]) {
+ defaultType = poolEntities.type;
+ defaultPool = splitPool[splitPool.length-1]
+ }
+ });
+ $.each(origDisks, function(index, diskEntities) {
+ var storageNodeData = {
+ viewMode : '',
+ editMode : 'hide',
+ storageName : defaultPool,
+ storageType : defaultType,
+ storageDisk : diskEntities.size
+ }
+ addStorageItem(storageNodeData);
+ });
+ }
+
+ $('#template-edit-storage-add-button').button({
+ icons: {
+ primary: "ui-icon-plusthick"
+ },
+ text: false,
+ disabled: true
+ }).click(function(event) {
+ event.preventDefault();
+ var storageNodeData = {
+ viewMode : 'hide',
+ editMode : '',
+ storageName : 'default',
+ storageType : 'dir',
+ storageDisk : ''
}
+ addStorageItem(storageNodeData);
});
- kimchi.listNetworks(function(result) {
- if(result && result.length > 0) {
- var html = '';
- var tmpl = $('#tmpl-network').html();
- $.each(result, function(index, network) {
- if (result[index].state === 'active')
- html += kimchi.substitute(tmpl, network);
- });
- $('#template-edit-network-list').html(html).show();
- if(template.networks && template.networks.length > 0) {
- $('input[name="networks"]', templateEditForm).each(function(index, element) {
- var value = $(element).val();
- if(template.networks.indexOf(value) >= 0) {
- $(element).prop('checked', true);
- }
- });
+ };
+ var initInterface = function(result) {
+ var addInterfaceItem = function(networkData) {
+ var nodeInterface = $.parseHTML(kimchi.substitute($('#template-interface-tmpl').html(), networkData));
+ $('.template-tab-body', '#form-template-interface').append(nodeInterface);
+ $('.edit', '#form-template-interface').button({
+ icons : {primary : 'ui-icon-pencil'},
+ text : false,
+ disabled : true
+ });
+ $('.delete', '#form-template-interface').button({
+ icons : {primary : 'ui-icon-trash'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ $(this).parent().parent().remove();
+ });
+ $('.cancel', '#form-template-interface').button({
+ icons : {primary : 'ui-icon-arrowreturnthick-1-w'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ $(this).parent().parent().remove();
+ });
+ $('.save', '#form-template-interface').button({
+ icons : {primary : 'ui-icon-disk'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ var interItem = $(this).parent().parent();
+ $('.save', interItem).parent().hide();
+ $('.delete', interItem).parent().show();
+ var selectedInterface = $('select', interItem).val();
+ $('label', interItem).text(selectedInterface).show();
+ $('select', interItem).hide();
+ });
+ var networkOptions = '';
+ for(var i=0;i<result.length;i++){
+ if(result[i].state === "active") {
+ var isSlected = i==0 ? ' selected' : '';
+ networkOptions += '<option' + isSlected + '>' + result[i].name + '</option>';
}
- } else {
- $('#template-edit-network-list').hide();
}
- });
- });
-
- $('#template-edit-storagePool').change(function() {
- storagepool = $(this).val();
- var storageArray = storagepool.split("/");
- if (storageArray.length > 3) {
- volumeName = storageArray.pop();
- poolName = storageArray.pop();
- kimchi.getStoragePoolVolume(poolName, volumeName, function(result) {
- $('input[name="disks"]', templateEditForm).val(result.capacity / Math.pow(1024,3));
- $('input[name="disks"]', templateEditForm).attr('disabled','disabled');
- return false;
- }, function (err) {
- kimchi.message.error(err.responseJSON.reason);
+ $('select', '#form-template-interface').find('option').remove();
+ $('select', '#form-template-interface').append(networkOptions);
+ };
+ if(result && result.length > 0) {
+ $.each(result, function(index, data) {
+ if($.inArray(data.name, origNetworks) > -1) {
+ addInterfaceItem({
+ mac : '',
+ network : data.name,
+ type : 'network',
+ viewMode : '',
+ editMode : 'hide'
+ });
+ }
});
- } else {
- $('input[name="disks"]', templateEditForm).removeAttr('disabled');
- $('input[name="disks"]', templateEditForm).val(templateDiskSize);
}
- });
- $('input[name="disks"]', templateEditForm).keyup(function() {
- templateDiskSize = $('input[name="disks"]', templateEditForm).val();
- });
+ $('#template-edit-interface-add-button').button({
+ icons: {
+ primary: 'ui-icon-plusthick'
+ },
+ text: false
+ }).click(function(evt) {
+ evt.preventDefault();
+ addInterfaceItem({
+ mac : '',
+ network : '',
+ type : 'network',
+ viewMode : 'hide',
+ editMode : ''
+ });
+ });
+ };
+ kimchi.retrieveTemplate(kimchi.selectedTemplate, initGeneral);
+ kimchi.listNetworks(initInterface);
+ kimchi.listStoragePools(initStorage);
$('#tmpl-edit-button-save').on('click', function() {
var editableFields = [ 'name', 'cpus', 'memory', 'storagepool', 'disks', 'graphics'];
@@ -147,33 +275,41 @@ kimchi.template_edit_main = function() {
$.each(editableFields, function(i, field) {
/* Support only 1 disk at this moment */
if (field == 'disks') {
- origDisks[0].size = Number($('#form-template-edit [name="' + field + '"]').val());
+ $.each($('#form-template-storage .item'), function(index, diskEntities) {
+ origDisks[index].size = Number($(this).find('.template-storage-disk').text());
+ })
data[field] = origDisks;
}
else if (field == 'graphics') {
- var type = $('#form-template-edit [name="' + field + '"]').val();
+ var type = $('#form-template-general [name="' + field + '"]').val();
data[field] = {'type': type};
}
else {
- data[field] = $('#form-template-edit [name="' + field + '"]').val();
+ data[field] = $('#form-template-general [name="' + field + '"]').val();
}
});
data['memory'] = Number(data['memory']);
data['cpus'] = Number(data['cpus']);
- storagepool = data['storagepool'];
- storageArray = storagepool.split("/");
- if (storageArray.length > 3){
- /* Support only 1 disk at this moment */
- data["disks"][0].volume = storageArray.pop();
- data['storagepool'] = storageArray.join("/");
- } else if (data["disks"][0].volume) {
- delete data["disks"][0].volume;
- }
- var networks = templateEditForm.serializeObject().networks;
- if (networks instanceof Array) {
- data.networks = networks;
- } else if (networks != null) {
- data.networks = [networks];
+
+ //Fix me: Only support one storage pool now
+ var storages = $('.template-tab-body .item', '#form-template-storage');
+ var storageForUpdate = new Array();
+ $.each(storages, function(index, storageEntities) {
+ var tempName = $('.template-storage-name', storageEntities).text()
+ tempName = '/storagepools/' + tempName;
+ storageForUpdate.push(tempName);
+ })
+ data['storagepool'] = storageForUpdate[0];
+
+ var networks = $('.template-tab-body .item', '#form-template-interface');
+ var networkForUpdate = new Array();
+ $.each(networks, function(index, networkEntities) {
+ networkForUpdate.push($('.template-interface-name', networkEntities).text());
+ });
+ if (networkForUpdate instanceof Array) {
+ data.networks = networkForUpdate;
+ } else if (networkForUpdate != null) {
+ data.networks = [networkForUpdate];
} else {
data.networks = [];
}
diff --git a/ui/pages/template-edit.html.tmpl b/ui/pages/template-edit.html.tmpl
index 5a71d91..d5d2be1 100644
--- a/ui/pages/template-edit.html.tmpl
+++ b/ui/pages/template-edit.html.tmpl
@@ -28,74 +28,67 @@
<div class="close">X</div>
</header>
<div class="content">
- <form id="form-template-edit">
+ <div id="edit-template-tabs">
<input type="hidden" id="template-name" name="templateName" />
- <fieldset class="template-edit-fieldset">
- <div>
+ <ul>
+ <li>
+ <a href="#form-template-general">$_("General")</a>
+ </li>
+ <li>
+ <a href="#form-template-storage">$_("storage")</a>
+ </li>
+ <li>
+ <a href="#form-template-interface">$_("Interface")</a>
+ </li>
+ </ul>
+ <form id="form-template-general">
+ <div class="form-template-inline-wrapper">
<div class="template-edit-wrapper-label">
<label for="template-edit-id-textbox">$_("Name")</label>
</div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-id-textbox" name="name" type="text" />
- </div>
- </div>
- <div>
<div class="template-edit-wrapper-label">
<label for="template-edit-vendor-textbox">$_("Vendor")</label>
</div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-vendor-textbox" name="os_distro" type="text" disabled="disabled" />
- </div>
- </div>
- <div>
<div class="template-edit-wrapper-label">
<label for="template-edit-version-textbox">$_("Version")</label>
</div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-version-textbox" name="os_version" type="text" disabled="disabled" />
- </div>
- </div>
- <div>
<div class="template-edit-wrapper-label">
<label for="template-edit-cpu-textbox">$_("CPU Number")</label>
</div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-cpu-textbox" name="cpus" type="text" />
- </div>
- </div>
- <div>
<div class="template-edit-wrapper-label">
<label for="template-edit-memory-textbox">$_("Memory (MB)")</label>
</div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-memory-textbox" name="memory" type="text" />
+ <div class="template-edit-wrapper-label templ-edit-cdrom">
+ <label for="template-edit-cdrom-textbox">$_("CDROM")</label>
+ </div>
+ <div class="template-edit-wrapper-label templ-edit-vm-image hide-content">
+ <label for="template-edit-vmimage-textbox">$_("Image File")</label>
</div>
- </div>
- <div>
<div class="template-edit-wrapper-label">
- <label for="template-edit-disk-textbox">$_("Disk (GB)")</label>
+ <label>$_("Graphics")</label>
</div>
+ </div>
+ <div class="form-template-inline-wrapper">
<div class="template-edit-wrapper-controls">
- <input id="template-edit-disk-textbox" name="disks" type="text" />
+ <input id="template-edit-id-textbox" name="name" type="text" />
</div>
- </div>
- </fieldset>
- <fieldset class="template-edit-fieldset">
- <div id="templ-edit-cdrom">
- <div class="template-edit-wrapper-label">
- <label for="template-edit-cdrom-textbox">$_("CDROM")</label>
+ <div class="template-edit-wrapper-controls">
+ <input id="template-edit-vendor-textbox" name="os_distro" type="text" disabled="disabled" />
</div>
<div class="template-edit-wrapper-controls">
- <input id="template-edit-cdrom-textbox" name="cdrom" type="text" disabled="disabled"/>
+ <input id="template-edit-version-textbox" name="os_version" type="text" disabled="disabled" />
</div>
- </div>
- <div id="templ-edit-vm-image" class="hide-content">
- <div class="template-edit-wrapper-label">$_("Image File")</div>
- <div class="template-edit-wrapper-controls"><input name="vm-image" type="text" disabled/></div>
- </div>
- <div>
- <div class="template-edit-wrapper-label">
- <label>$_("Graphics")</label>
+ <div class="template-edit-wrapper-controls">
+ <input id="template-edit-cpu-textbox" name="cpus" type="text" />
+ </div>
+ <div class="template-edit-wrapper-controls">
+ <input id="template-edit-memory-textbox" name="memory" type="text" />
+ </div>
+ <div class="template-edit-wrapper-controls templ-edit-cdrom">
+ <input id="template-edit-cdrom-textbox" name="cdrom" type="text" disabled="disabled" />
+ </div>
+ <div class="template-edit-wrapper-controls templ-edit-vm-image hide-content">
+ <input id="template-edit-vmimage-textbox" name="vm-image" type="text" disabled="disabled" />
</div>
<div class="template-edit-wrapper-controls">
<div class="btn dropdown popable">
@@ -108,40 +101,26 @@
</div>
</div>
</div>
- <div>
- <div class="template-edit-wrapper-label">
- <label>$_("Storage Pool")</label>
- </div>
- <div class="template-edit-wrapper-controls">
- <div class="btn dropdown popable">
- <input id="template-edit-storagePool" name="storagepool" type="hidden" />
- <span class="text" id="template-edit-storage-label"></span><span class="arrow"></span>
- <div class="popover" style="width: 100%">
- <ul class="select-list" id="template-edit-storagePool-list" data-target="template-edit-storagePool" data-label="template-edit-storage-label">
- </ul>
- </div>
- </div>
- </div>
+ </form>
+ <form id="form-template-storage">
+ <div class="template-tab-header">
+ <span class="template-storage-cell">$_("Storage Pool")</span>
+ <span class="template-storage-cell">$_("Type")</span>
+ <span class="template-storage-cell">$_("Disk(GB)")</span>
+ <button type="button" id="template-edit-storage-add-button" class="action-area"></button>
</div>
- <div>
- <div class="template-edit-wrapper-label">
- <label>$_("Network")</label>
- </div>
- <div class="template-edit-wrapper-controls">
- <ul class="select-list-box" id="template-edit-network-list">
- </ul>
- <script id="tmpl-network" type="text/html">
- <li>
- <label>
- <input name="networks" type="checkbox" value="{name}" />
- <span class="item">{name}</span>
- </label>
- </li>
- </script>
- </div>
+ <div class="template-tab-body">
</div>
- </fieldset>
- </form>
+ </form>
+ <form id="form-template-interface">
+ <div class="template-tab-header">
+ <span class="template-interface-cell">$_("Network")</span>
+ <span class="template-interface-cell">$_("Type")</span>
+ <button type="button" id="template-edit-interface-add-button" class="action-area"></button>
+ </div>
+ <div class="template-tab-body"></div>
+ </form>
+ </div>
</div>
<footer>
<div class="btn-group">
@@ -152,3 +131,41 @@
<script>
kimchi.template_edit_main();
</script>
+<script id="template-storage-pool-tmpl" type="text/html">
+ <div class='item'>
+ <span class="template-storage-cell">
+ <label class="template-storage-name {viewMode}">{storageName}</label>
+ <select class="{editMode}"></select>
+ </span>
+ <span class="template-storage-cell">
+ <label class="template-storage-type">{storageType}</label>
+ </span>
+ <span class="template-storage-cell">
+ <label class="template-storage-disk {viewMode}">{storageDisk}</label>
+ <input class="{editMode}" />
+ </span>
+ <span class="action-area {editMode}">
+ <button class="save"></button><button class="cancel"></button>
+ </span>
+ <span class="action-area {viewMode}">
+ <button class="edit"></button><button class="delete"></button>
+ </span>
+ </div>
+</script>
+<script id="template-interface-tmpl" type="text/html">
+ <div class="item">
+ <span class="template-interface-cell">
+ <label class="template-interface-name {viewMode}">{network}</label>
+ <select class="{editMode}"></select>
+ </span>
+ <span class="template-interface-cell">
+ <span>{type}</span>
+ </span>
+ <span class="action-area {editMode}">
+ <button class="save"></button><button class="cancel"></button>
+ </span>
+ <span class="action-area {viewMode}">
+ <button class="edit"></button><button class="delete"></button>
+ </span>
+ </div>
+</script>
\ No newline at end of file
--
1.7.1
10 years