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/guest-edit.css | 34 +++++++++++-
ui/js/src/kimchi.api.js | 69 ++++++++++++++++++++++
ui/js/src/kimchi.guest_edit_main.js | 107 +++++++++++++++++++++++++++++++++++
ui/pages/guest-edit.html.tmpl | 27 +++++++++
4 files changed, 236 insertions(+), 1 deletions(-)
diff --git a/ui/css/theme-default/guest-edit.css b/ui/css/theme-default/guest-edit.css
index b1d3931..a565c99 100644
--- a/ui/css/theme-default/guest-edit.css
+++ b/ui/css/theme-default/guest-edit.css
@@ -96,6 +96,7 @@
}
#form-guest-edit-storage .header,
+.guest-edit-snapshot .header,
.guest-edit-interface .header {
margin-bottom: 8px;
padding-bottom: 2px;
@@ -105,6 +106,7 @@
}
#form-guest-edit-storage .body .item,
+.guest-edit-snapshot .body .item,
.guest-edit-interface .body .item {
margin: 5px 0;
}
@@ -115,6 +117,32 @@
width: 250px;
}
+.guest-edit-snapshot .cell {
+ display: inline-block;
+}
+
+.guest-edit-snapshot .sel {
+ width: 35px;
+ vertical-align: top;
+}
+
+.guest-edit-snapshot .icon {
+ background: url('../../images/theme-default/kimchi-loading15x15.gif')
no-repeat;
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ vertical-align: middle;
+ margin-left: 2px;
+}
+
+.guest-edit-snapshot .name {
+ width: 400px;
+}
+
+.guest-edit-snapshot .created {
+ width: 250px;
+}
+
#form-guest-edit-storage .cell.dev {
width: 60px;
}
@@ -135,6 +163,7 @@
}
#form-guest-edit-storage .action-area,
+.guest-edit-snapshot .action-area,
.guest-edit-interface .action-area {
float: right;
}
@@ -144,12 +173,14 @@
}
#form-guest-edit-storage button,
+.guest-edit-snapshot button,
.guest-edit-interface button {
width: 20px;
height: 20px;
}
#form-guest-edit-storage .header button,
+.guest-edit-snapshot .header button,
.guest-edit-interface .header button {
margin-bottom: 1px;
}
@@ -159,8 +190,9 @@
margin-right: 2px;
}
+.guest-edit-snapshot .hide,
.guest-edit-interface .hide {
- display: none;
+ display: none!important;
}
.guest-edit-permission {
diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js
index 78c6d66..a592298 100644
--- a/ui/js/src/kimchi.api.js
+++ b/ui/js/src/kimchi.api.js
@@ -1260,5 +1260,74 @@ var kimchi = {
kimchi.message.error(data.responseJSON.reason);
}
});
+ },
+
+ listSnapshots : function(vm, suc, err) {
+ kimchi.requestJSON({
+ url : kimchi.url+'vms/'+encodeURIComponent(vm)+'/snapshots',
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ resend : true,
+ success : suc,
+ error : err ? err : function(data) {
+ kimchi.message.error(data.responseJSON.reason);
+ }
+ });
+ },
+
+ getCurrentSnapshot : function(vm, suc, err, sync) {
+ kimchi.requestJSON({
+ url :
kimchi.url+'vms/'+encodeURIComponent(vm)+'/snapshots/current',
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ async : !sync,
+ resend : true,
+ success : suc,
+ error : err ? err : function(data) {
+ if(data.status!=404) kimchi.message.error(data.responseJSON.reason);
+ }
+ });
+ },
+
+ revertSnapshot : function(vm, snapshot, suc, err) {
+ kimchi.requestJSON({
+ url :
kimchi.url+'vms/'+encodeURIComponent(vm)+'/snapshots/'+encodeURIComponent(snapshot)+'/revert',
+ type : 'POST',
+ contentType : 'application/json',
+ dataType : 'json',
+ success : suc,
+ error : err ? err : function(data) {
+ kimchi.message.error(data.responseJSON.reason);
+ }
+ });
+ },
+
+ createSnapshot : function(vm, suc, err) {
+ kimchi.requestJSON({
+ url : kimchi.url+'vms/'+encodeURIComponent(vm)+'/snapshots',
+ type : 'POST',
+ contentType : 'application/json',
+ dataType : 'json',
+ data : JSON.stringify({}),
+ success : suc,
+ error : err ? err : function(data) {
+ kimchi.message.error(data.responseJSON.reason);
+ }
+ });
+ },
+
+ deleteSnapshot : function(vm, snapshot, suc, err) {
+ kimchi.requestJSON({
+ url :
kimchi.url+'vms/'+encodeURIComponent(vm)+'/snapshots/'+encodeURIComponent(snapshot),
+ type : 'DELETE',
+ 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_edit_main.js b/ui/js/src/kimchi.guest_edit_main.js
index 9d87a73..0dc3a31 100644
--- a/ui/js/src/kimchi.guest_edit_main.js
+++ b/ui/js/src/kimchi.guest_edit_main.js
@@ -461,6 +461,112 @@ kimchi.guest_edit_main = function() {
});
};
+ var setupSnapshot = function() {
+ var currentSnapshot;
+ var postTask = function(){
+ listGeneratingSnapshots();
+ $("button",
"#form-guest-edit-snapshot").button("enable");
+ };
+ var addItem = function(data, container) {
+ var itemNode =
$.parseHTML(kimchi.substitute($('#snapshot-tmpl').html(),data));
+ $("."+container,
"#form-guest-edit-snapshot").append(itemNode);
+ if(kimchi.thisVMState === "running") {
+ $("button", itemNode).remove();
+ }else{
+ $(".delete", itemNode).button({
+ icons: { primary: "ui-icon-trash" },
+ text: false
+ }).click(function(evt){
+ evt.preventDefault();
+ var item = $(this).parent().parent();
+ kimchi.deleteSnapshot(kimchi.selectedGuest,
item.prop("id"), function(){
+ item.remove();
+ });
+ });
+ $(".revert", itemNode).button({
+ icons: { primary: "ui-icon-arrowthick-1-ne" },
+ text: false
+ }).click(function(evt){
+ evt.preventDefault();
+ var item = $(this).parent().parent();
+ $(".ui-icon-check", item).addClass("hide");
+ $(".icon", item).removeClass("hide");
+ $("button",
"#form-guest-edit-snapshot").button("disable");
+ kimchi.revertSnapshot(kimchi.selectedGuest,
item.prop("id"), function(){
+ currentSnapshot = item.prop("id");
+ $(".ui-icon-check", $(".body",
"#form-guest-edit-snapshot")).addClass("hide");
+ $(".ui-icon-check",
item).removeClass("hide");
+ $(".icon", item).addClass("hide");
+ $("button",
"#form-guest-edit-snapshot").button("enable");
+ }, function(data){
+ kimchi.message.error(data.responseJSON.reason);
+ $(".icon", item).addClass("hide");
+ if(currentSnapshot) $(".ui-icon-check",
currentSnapshot).removeClass("hide");
+ $("button",
"#form-guest-edit-snapshot").button("enable");
+ });
+ });
+ }
+ return itemNode;
+ };
+ var addOngoingItem = function(task){
+ var uri = task.target_uri;
+ addItem({
+ name: uri.substring(uri.lastIndexOf('/')+1, uri.length),
+ created: "",
+ current: "hide",
+ listMode: "hide",
+ createMode: ""
+ }, 'task');
+ if(kimchi.trackingTasks.indexOf(task.id)==-1)
+ kimchi.trackTask(task.id, function(task){
+ postTask();
+ }, function(err){
+ kimchi.message.error(err.message);
+ postTask();
+ });
+ };
+ var listGeneratingSnapshots = function(){
+ $(".task", "#form-guest-edit-snapshot").empty();
+
kimchi.getTasksByFilter('status=running&target_uri='+encodeURIComponent('^/snapshots/*'),
function(tasks) {
+ for(var i=0;i<tasks.length;i++){
+ addOngoingItem(tasks[i]);
+ }
+ if(tasks.length==0) listSnapshots();
+ });
+ };
+ var listSnapshots = function(){
+ $(".body", "#form-guest-edit-snapshot").empty();
+ kimchi.getCurrentSnapshot(kimchi.selectedGuest, function(snapshot){
+ currentSnapshot = snapshot.name;
+ });
+ kimchi.listSnapshots(kimchi.selectedGuest, function(data){
+ for(var i=0;i<data.length;i++){
+ data[i].created = new Date(data[i].created*1000).toLocaleString();
+ data[i].current =
data[i].name==currentSnapshot?"":"hide";
+ data[i].createMode = "hide";
+ data[i].listMode = "";
+ var item = addItem(data[i], 'body');
+ }
+ });
+ };
+ listGeneratingSnapshots();
+ if(kimchi.thisVMState === "running") {
+ $("#form-guest-edit-snapshot .revert").remove();
+ $("#form-guest-edit-snapshot .add").remove();
+ }else{
+ $(".add", "#form-guest-edit-snapshot").button({
+ icons: { primary: "ui-icon-plusthick" },
+ text: false
+ }).click(function(evt){
+ evt.preventDefault();
+ $("button",
"#form-guest-edit-snapshot").button("disable");
+ kimchi.createSnapshot(kimchi.selectedGuest, function(task){
+ addOngoingItem(task);
+ });
+ });
+ }
+ };
+
var initContent = function(guest) {
guest['icon'] = guest['icon'] || 'images/icon-vm.png';
$('#form-guest-edit-general').fillWithObject(guest);
@@ -496,6 +602,7 @@ kimchi.guest_edit_main = function() {
setupInterface();
setupPermission();
setupPCIDevice();
+ setupSnapshot();
kimchi.topic('kimchi/vmCDROMAttached').subscribe(onAttached);
kimchi.topic('kimchi/vmCDROMReplaced').subscribe(onReplaced);
diff --git a/ui/pages/guest-edit.html.tmpl b/ui/pages/guest-edit.html.tmpl
index 512909a..dd320b9 100644
--- a/ui/pages/guest-edit.html.tmpl
+++ b/ui/pages/guest-edit.html.tmpl
@@ -44,6 +44,9 @@
<li>
<a href="#form-guest-edit-pci">$_("Host PCI
Device")</a>
</li>
+ <li>
+ <a
href="#form-guest-edit-snapshot">$_("Snapshot")</a>
+ </li>
</ul>
<form id="form-guest-edit-general">
<fieldset class="guest-edit-fieldset">
@@ -160,6 +163,16 @@
<div class="body"></div>
</div>
</form>
+ <form id="form-guest-edit-snapshot"
class="guest-edit-snapshot">
+ <div class="header">
+ <span class="cell sel"></span>
+ <span class="cell
name">$_("Name")</span>
+ <span class="cell
created">$_("Created")</span>
+ <button class="add action-area"></button>
+ </div>
+ <div class="task"></div>
+ <div class="body"></div>
+ </form>
</div>
</div>
<footer>
@@ -251,6 +264,20 @@
<button></button>
</div>
</script>
+<script id="snapshot-tmpl" type="text/html">
+ <div class="item" id="{name}">
+ <span class="cell sel">
+ <span class="icon {createMode}"></span>
+ <span class="ui-icon ui-icon-check {current}"></span>
+ </span>
+ <span class="cell name">{name}</span>
+ <span class="cell created">{created}</span>
+ <span class="action-area">
+ <button class="revert {listMode}"
title="$_("revert")"></button>
+ <button class="delete {listMode}"></button>
+ </span>
+ <div>
+</script>
<script type="text/javascript">
kimchi.guest_edit_main();
</script>
--
1.7.1