[pve-devel] [PATCH manager 2/3] gui: refator SnapshotTree

Dominik Csapak d.csapak at proxmox.com
Thu Jan 30 16:58:52 CET 2020


using the better View, ViewModel, Controller style,
while doing this, make it generic so that we can use it for qemu and lxc

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 www/manager6/Makefile             |   3 +-
 www/manager6/lxc/Config.js        |   3 +-
 www/manager6/lxc/SnapshotTree.js  | 330 ---------------------------
 www/manager6/qemu/Config.js       |   3 +-
 www/manager6/qemu/SnapshotTree.js | 320 --------------------------
 www/manager6/tree/SnapshotTree.js | 361 ++++++++++++++++++++++++++++++
 6 files changed, 366 insertions(+), 654 deletions(-)
 delete mode 100644 www/manager6/lxc/SnapshotTree.js
 delete mode 100644 www/manager6/qemu/SnapshotTree.js
 create mode 100644 www/manager6/tree/SnapshotTree.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 3c99ec6c..eb7ac004 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -98,6 +98,7 @@ JSSRC= 				                 	\
 	grid/FirewallAliases.js				\
 	grid/FirewallOptions.js				\
 	tree/ResourceTree.js				\
+	tree/SnapshotTree.js				\
 	panel/IPSet.js					\
 	panel/ConfigPanel.js				\
 	grid/BackupView.js				\
@@ -147,7 +148,6 @@ JSSRC= 				                 	\
 	qemu/ScsiHwEdit.js				\
 	qemu/QemuBiosEdit.js				\
 	qemu/Options.js					\
-	qemu/SnapshotTree.js				\
 	qemu/Config.js					\
 	qemu/CreateWizard.js				\
 	qemu/USBEdit.js					\
@@ -167,7 +167,6 @@ JSSRC= 				                 	\
 	lxc/DNS.js					\
 	lxc/Config.js					\
 	lxc/CreateWizard.js				\
-	lxc/SnapshotTree.js				\
 	lxc/ResourceEdit.js				\
 	lxc/MPResize.js					\
 	lxc/MPEdit.js					\
diff --git a/www/manager6/lxc/Config.js b/www/manager6/lxc/Config.js
index e14b5ad2..29464706 100644
--- a/www/manager6/lxc/Config.js
+++ b/www/manager6/lxc/Config.js
@@ -266,7 +266,8 @@ Ext.define('PVE.lxc.Config', {
 	    me.items.push({
 		title: gettext('Snapshots'),
 		iconCls: 'fa fa-history',
-		xtype: 'pveLxcSnapshotTree',
+		xtype: 'pveGuestSnapshotTree',
+		type: 'lxc',
 		itemId: 'snapshot'
 	    });
 	}
diff --git a/www/manager6/lxc/SnapshotTree.js b/www/manager6/lxc/SnapshotTree.js
deleted file mode 100644
index 0c1d9a6b..00000000
--- a/www/manager6/lxc/SnapshotTree.js
+++ /dev/null
@@ -1,330 +0,0 @@
-Ext.define('PVE.lxc.SnapshotTree', {
-    extend: 'Ext.tree.Panel',
-    alias: ['widget.pveLxcSnapshotTree'],
-
-    onlineHelp: 'pct_snapshots',
-
-    load_delay: 3000,
-
-    old_digest: 'invalid',
-
-    stateful: true,
-    stateId: 'grid-lxc-snapshots',
-
-    sorterFn: function(rec1, rec2) {
-	var v1 = rec1.data.snaptime;
-	var v2 = rec2.data.snaptime;
-
-	if (rec1.data.name === 'current') {
-	    return 1;
-	}
-	if (rec2.data.name === 'current') {
-	    return -1;
-	}
-
-	return (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0));
-    },
-
-    reload: function(repeat) {
-	var me = this;
-
-	Proxmox.Utils.API2Request({
-	    url: '/nodes/' + me.nodename + '/lxc/' + me.vmid + '/snapshot',
-	    method: 'GET',
-	    failure: function(response, opts) {
-		Proxmox.Utils.setErrorMask(me, response.htmlStatus);
-		me.load_task.delay(me.load_delay);
-	    },
-	    success: function(response, opts) {
-		Proxmox.Utils.setErrorMask(me, false);
-		var digest = 'invalid';
-		var idhash = {};
-		var root = { name: '__root', expanded: true, children: [] };
-		Ext.Array.each(response.result.data, function(item) {
-		    item.leaf = true;
-		    item.children = [];
-		    if (item.name === 'current') {
-			digest = item.digest + item.running;
-			if (item.running) {
-			    item.iconCls = 'fa fa-fw fa-desktop x-fa-tree-running';
-			} else {
-			    item.iconCls = 'fa fa-fw fa-desktop x-fa-tree';
-			}
-		    } else {
-			item.iconCls = 'fa fa-fw fa-history x-fa-tree';
-		    }
-		    idhash[item.name] = item;
-		});
-
-		if (digest !== me.old_digest) {
-		    me.old_digest = digest;
-
-		    Ext.Array.each(response.result.data, function(item) {
-			if (item.parent && idhash[item.parent]) {
-			    var parent_item = idhash[item.parent];
-			    parent_item.children.push(item);
-			    parent_item.leaf = false;
-			    parent_item.expanded = true;
-			    parent_item.expandable = false;
-			} else {
-			    root.children.push(item);
-			}
-		    });
-
-		    me.setRootNode(root);
-		}
-
-		me.load_task.delay(me.load_delay);
-	    }
-	});
-
-	Proxmox.Utils.API2Request({
-	    url: '/nodes/' + me.nodename + '/lxc/' + me.vmid + '/feature',
-	    params: { feature: 'snapshot' },
-	    method: 'GET',
-	    success: function(response, options) {
-		var res = response.result.data;
-		if (res.hasFeature) {
-		    var snpBtns = Ext.ComponentQuery.query('#snapshotBtn');
-		    snpBtns.forEach(function(item){
-			item.enable();
-		    });
-		}
-	    }
-	});
-
-
-    },
-
-    listeners: {
-	beforestatesave: function(grid, state, eopts) {
-	    // extjs cannot serialize functions,
-	    // so a the sorter with only the sorterFn will
-	    // not be a valid sorter when restoring the state
-	    delete state.storeState.sorters;
-	}
-    },
-
-    initComponent: function() {
-	var me = this;
-
-	me.nodename = me.pveSelNode.data.node;
-	if (!me.nodename) {
-	    throw "no node name specified";
-	}
-
-	me.vmid = me.pveSelNode.data.vmid;
-	if (!me.vmid) {
-	    throw "no VM ID specified";
-	}
-
-	me.load_task = new Ext.util.DelayedTask(me.reload, me);
-
-	var sm = Ext.create('Ext.selection.RowModel', {});
-
-	var valid_snapshot = function(record) {
-	    return record && record.data && record.data.name &&
-		record.data.name !== 'current';
-	};
-
-	var valid_snapshot_rollback = function(record) {
-	    return record && record.data && record.data.name &&
-		record.data.name !== 'current' && !record.data.snapstate;
-	};
-
-	var run_editor = function() {
-	    var rec = sm.getSelection()[0];
-	    if (valid_snapshot(rec)) {
-		var win = Ext.create('PVE.window.LxcSnapshot', {
-		    type: 'lxc',
-		    snapname: rec.data.name,
-		    nodename: me.nodename,
-		    vmid: me.vmid
-		});
-		win.show();
-		me.mon(win, 'close', me.reload, me);
-	    }
-	};
-
-	var editBtn = new Proxmox.button.Button({
-	    text: gettext('Edit'),
-	    disabled: true,
-	    selModel: sm,
-	    enableFn: valid_snapshot,
-	    handler: run_editor
-	});
-
-	var rollbackBtn = new Proxmox.button.Button({
-	    text: gettext('Rollback'),
-	    disabled: true,
-	    dangerous: true,
-	    selModel: sm,
-	    enableFn: valid_snapshot_rollback,
-	    confirmMsg: function(rec) {
-		var taskdescription = Proxmox.Utils.format_task_description('vzrollback', me.vmid);
-		var snaptime = Ext.Date.format(rec.data.snaptime,'Y-m-d H:i:s');
-		var snapname = rec.data.name;
-
-		var msg = Ext.String.format(gettext('{0} to {1} ({2})'),
-		                            taskdescription, snapname, snaptime);
-		msg += '<p>' + gettext('Note: Rollback stops CT') + '</p>';
-
-		return msg;
-	    },
-	    handler: function(btn, event) {
-		var rec = sm.getSelection()[0];
-		if (!rec) {
-		    return;
-		}
-		var snapname = rec.data.name;
-
-		Proxmox.Utils.API2Request({
-		    url: '/nodes/' + me.nodename + '/lxc/' + me.vmid + '/snapshot/' + snapname + '/rollback',
-		    method: 'POST',
-		    waitMsgTarget: me,
-		    callback: function() {
-			me.reload();
-		    },
-		    failure: function (response, opts) {
-			Ext.Msg.alert(gettext('Error'), response.htmlStatus);
-		    },
-		    success: function(response, options) {
-			var upid = response.result.data;
-			var win = Ext.create('Proxmox.window.TaskProgress', { upid: upid });
-			win.show();
-		    }
-		});
-	    }
-	});
-
-	var removeBtn = new Proxmox.button.Button({
-	    text: gettext('Remove'),
-	    disabled: true,
-	    selModel: sm,
-	    confirmMsg: function(rec) {
-		var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
-					    "'" + rec.data.name + "'");
-		return msg;
-	    },
-	    enableFn: valid_snapshot,
-	    handler: function(btn, event) {
-		var rec = sm.getSelection()[0];
-		if (!rec) {
-		    return;
-		}
-		var snapname = rec.data.name;
-
-		Proxmox.Utils.API2Request({
-		    url: '/nodes/' + me.nodename + '/lxc/' + me.vmid + '/snapshot/' + snapname,
-		    method: 'DELETE',
-		    waitMsgTarget: me,
-		    callback: function() {
-			me.reload();
-		    },
-		    failure: function (response, opts) {
-			Ext.Msg.alert(gettext('Error'), response.htmlStatus);
-		    },
-		    success: function(response, options) {
-			var upid = response.result.data;
-			var win = Ext.create('Proxmox.window.TaskProgress', { upid: upid });
-			win.show();
-		    }
-		});
-	    }
-	});
-
-	var snapshotBtn = Ext.create('Ext.Button', {
-	    itemId: 'snapshotBtn',
-	    text: gettext('Take Snapshot'),
-	    disabled: true,
-	    handler: function() {
-		var win = Ext.create('PVE.window.LxcSnapshot', {
-		    type: 'lxc',
-		    isCreate: true,
-		    submitText: gettext('Take Snapshot'),
-		    nodename: me.nodename,
-		    vmid: me.vmid
-		});
-		win.show();
-	    }
-	});
-
-	Ext.apply(me, {
-	    layout: 'fit',
-	    rootVisible: false,
-	    animate: false,
-	    sortableColumns: false,
-	    selModel: sm,
-	    tbar: [ snapshotBtn, rollbackBtn, removeBtn, editBtn ],
-	    fields: [
-		'name', 'description', 'snapstate', 'vmstate', 'running',
-		{ name: 'snaptime', type: 'date', dateFormat: 'timestamp' }
-	    ],
-	    columns: [
-		{
-		    xtype: 'treecolumn',
-		    text: gettext('Name'),
-		    dataIndex: 'name',
-		    width: 200,
-		    renderer: function(value, metaData, record) {
-			if (value === 'current') {
-			    return "NOW";
-			} else {
-			    return value;
-			}
-		    }
-		},
-//		{
-//		    text: gettext('RAM'),
-//		    align: 'center',
-//		    resizable: false,
-//		    dataIndex: 'vmstate',
-//		    width: 50,
-//		    renderer: function(value, metaData, record) {
-//			if (record.data.name !== 'current') {
-//			    return Proxmox.Utils.format_boolean(value);
-//			}
-//		    }
-//		},
-		{
-		    text: gettext('Date') + "/" + gettext("Status"),
-		    dataIndex: 'snaptime',
-		    resizable: false,
-		    width: 150,
-		    renderer: function(value, metaData, record) {
-			if (record.data.snapstate) {
-			    return record.data.snapstate;
-			}
-			if (value) {
-			    return Ext.Date.format(value,'Y-m-d H:i:s');
-			}
-		    }
-		},
-		{
-		    text: gettext('Description'),
-		    dataIndex: 'description',
-		    flex: 1,
-		    renderer: function(value, metaData, record) {
-			if (record.data.name === 'current') {
-			    return gettext("You are here!");
-			} else {
-			    return Ext.String.htmlEncode(value);
-			}
-		    }
-		}
-	    ],
-	    columnLines: true,
-	    listeners: {
-		activate: me.reload,
-		destroy: me.load_task.cancel,
-		itemdblclick: run_editor
-	    }
-	});
-
-	me.callParent();
-
-	me.store.sorters.add(new Ext.util.Sorter({
-	    sorterFn: me.sorterFn
-	}));
-    }
-});
diff --git a/www/manager6/qemu/Config.js b/www/manager6/qemu/Config.js
index 195db3a2..ea8b6137 100644
--- a/www/manager6/qemu/Config.js
+++ b/www/manager6/qemu/Config.js
@@ -298,7 +298,8 @@ Ext.define('PVE.qemu.Config', {
 	    me.items.push({
 		title: gettext('Snapshots'),
 		iconCls: 'fa fa-history',
-		xtype: 'pveQemuSnapshotTree',
+		type: 'qemu',
+		xtype: 'pveGuestSnapshotTree',
 		itemId: 'snapshot'
 	    });
 	}
diff --git a/www/manager6/qemu/SnapshotTree.js b/www/manager6/qemu/SnapshotTree.js
deleted file mode 100644
index a3891433..00000000
--- a/www/manager6/qemu/SnapshotTree.js
+++ /dev/null
@@ -1,320 +0,0 @@
-Ext.define('PVE.qemu.SnapshotTree', {
-    extend: 'Ext.tree.Panel',
-    alias: ['widget.pveQemuSnapshotTree'],
-
-    load_delay: 3000,
-
-    old_digest: 'invalid',
-
-    stateful: true,
-    stateId: 'grid-qemu-snapshots',
-
-    sorterFn: function(rec1, rec2) {
-	var v1 = rec1.data.snaptime;
-	var v2 = rec2.data.snaptime;
-
-	if (rec1.data.name === 'current') {
-	    return 1;
-	}
-	if (rec2.data.name === 'current') {
-	    return -1;
-	}
-
-	return (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0));
-    },
-
-    reload: function(repeat) {
-        var me = this;
-
-	Proxmox.Utils.API2Request({
-	    url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + '/snapshot',
-	    method: 'GET',
-	    failure: function(response, opts) {
-		Proxmox.Utils.setErrorMask(me, response.htmlStatus);
-		me.load_task.delay(me.load_delay);
-	    },
-	    success: function(response, opts) {
-		Proxmox.Utils.setErrorMask(me, false);
-		var digest = 'invalid';
-		var idhash = {};
-		var root = { name: '__root', expanded: true, children: [] };
-		Ext.Array.each(response.result.data, function(item) {
-		    item.leaf = true;
-		    item.children = [];
-		    if (item.name === 'current') {
-			digest = item.digest + item.running;
-			if (item.running) {
-			    item.iconCls = 'fa fa-fw fa-desktop x-fa-tree-running';
-			} else {
-			    item.iconCls = 'fa fa-fw fa-desktop x-fa-tree';
-			}
-		    } else {
-			item.iconCls = 'fa fa-fw fa-history x-fa-tree';
-		    }
-		    idhash[item.name] = item;
-		});
-
-		if (digest !== me.old_digest) {
-		    me.old_digest = digest;
-
-		    Ext.Array.each(response.result.data, function(item) {
-			if (item.parent && idhash[item.parent]) {
-			    var parent_item = idhash[item.parent];
-			    parent_item.children.push(item);
-			    parent_item.leaf = false;
-			    parent_item.expanded = true;
-			    parent_item.expandable = false;
-			} else {
-			    root.children.push(item);
-			}
-		    });
-
-		    me.setRootNode(root);
-		}
-
-		me.load_task.delay(me.load_delay);
-	    }
-	});
-
-        Proxmox.Utils.API2Request({
-	    url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + '/feature',
-	    params: { feature: 'snapshot' },
-            method: 'GET',
-            success: function(response, options) {
-                var res = response.result.data;
-		if (res.hasFeature) {
-		    var snpBtns = Ext.ComponentQuery.query('#snapshotBtn');
-		    snpBtns.forEach(function(item){
-			item.enable();
-		    });
-		}
-            }
-        });
-
-
-    },
-
-    listeners: {
-	beforestatesave: function(grid, state, eopts) {
-	    // extjs cannot serialize functions,
-	    // so a the sorter with only the sorterFn will
-	    // not be a valid sorter when restoring the state
-	    delete state.storeState.sorters;
-	}
-    },
-
-    initComponent: function() {
-        var me = this;
-
-	me.nodename = me.pveSelNode.data.node;
-	if (!me.nodename) { 
-	    throw "no node name specified";
-	}
-
-	me.vmid = me.pveSelNode.data.vmid;
-	if (!me.vmid) {
-	    throw "no VM ID specified";
-	}
-
-	me.load_task = new Ext.util.DelayedTask(me.reload, me);
-
-	var sm = Ext.create('Ext.selection.RowModel', {});
-
-	var valid_snapshot = function(record) {
-	    return record && record.data && record.data.name &&
-		record.data.name !== 'current';
-	};
-
-	var valid_snapshot_rollback = function(record) {
-	    return record && record.data && record.data.name &&
-		record.data.name !== 'current' && !record.data.snapstate;
-	};
-
-	var run_editor = function() {
-	    var rec = sm.getSelection()[0];
-	    if (valid_snapshot(rec)) {
-		var win = Ext.create('PVE.window.Snapshot', { 
-		    type: 'qemu',
-		    snapname: rec.data.name,
-		    nodename: me.nodename,
-		    vmid: me.vmid
-		});
-		win.show();
-		me.mon(win, 'close', me.reload, me);
-	    }
-	};
-
-	var editBtn = new Proxmox.button.Button({
-	    text: gettext('Edit'),
-	    disabled: true,
-	    selModel: sm,
-	    enableFn: valid_snapshot,
-	    handler: run_editor
-	});
-
-	var rollbackBtn = new Proxmox.button.Button({
-	    text: gettext('Rollback'),
-	    disabled: true,
-	    selModel: sm,
-	    enableFn: valid_snapshot_rollback,
-	    confirmMsg: function(rec) {
-		return Proxmox.Utils.format_task_description('qmrollback', me.vmid) +
-		    " '" +  rec.data.name + "'";
-	    },
-	    handler: function(btn, event) {
-		var rec = sm.getSelection()[0];
-		if (!rec) {
-		    return;
-		}
-		var snapname = rec.data.name;
-
-		Proxmox.Utils.API2Request({
-		    url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + '/snapshot/' + snapname + '/rollback',
-		    method: 'POST',
-		    waitMsgTarget: me,
-		    callback: function() {
-			me.reload();
-		    },
-		    failure: function (response, opts) {
-			Ext.Msg.alert(gettext('Error'), response.htmlStatus);
-		    },
-		    success: function(response, options) {
-			var upid = response.result.data;
-			var win = Ext.create('Proxmox.window.TaskProgress', { upid: upid });
-			win.show();
-		    }
-		});
-	    }
-	});
-
-	var removeBtn = new Proxmox.button.Button({
-	    text: gettext('Remove'),
-	    disabled: true,
-	    selModel: sm,
-	    confirmMsg: function(rec) {
-		var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
-					    "'" + rec.data.name + "'");
-		return msg;
-	    },
-	    enableFn: valid_snapshot,
-	    handler: function(btn, event) {
-		var rec = sm.getSelection()[0];
-		if (!rec) {
-		    return;
-		}
-		var snapname = rec.data.name;
-
-		Proxmox.Utils.API2Request({
-		    url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + '/snapshot/' + snapname,
-		    method: 'DELETE',
-		    waitMsgTarget: me,
-		    callback: function() {
-			me.reload();
-		    },
-		    failure: function (response, opts) {
-			Ext.Msg.alert(gettext('Error'), response.htmlStatus);
-		    },
-		    success: function(response, options) {
-			var upid = response.result.data;
-			var win = Ext.create('Proxmox.window.TaskProgress', { upid: upid });
-			win.show();
-		    }
-		});
-	    }
-	});
-
-	var snapshotBtn = Ext.create('Ext.Button', { 
-	    itemId: 'snapshotBtn',
-	    text: gettext('Take Snapshot'),
-	    disabled: true,
-	    handler: function() {
-		var win = Ext.create('PVE.window.Snapshot', { 
-		    isCreate: true,
-		    type: 'qemu',
-		    submitText: gettext('Take Snapshot'),
-		    nodename: me.nodename,
-		    vmid: me.vmid
-		});
-		win.show();
-	    }
-	});
-
-	Ext.apply(me, {
-	    layout: 'fit',
-	    rootVisible: false,
-	    animate: false,
-	    sortableColumns: false,
-	    selModel: sm,
-	    tbar: [ snapshotBtn, rollbackBtn, removeBtn, editBtn ],
-	    fields: [ 
-		'name', 'description', 'snapstate', 'vmstate', 'running',
-		{ name: 'snaptime', type: 'date', dateFormat: 'timestamp' }
-	    ],
-	    columns: [
-		{
-		    xtype: 'treecolumn',
-		    text: gettext('Name'),
-		    dataIndex: 'name',
-		    width: 200,
-		    renderer: function(value, metaData, record) {
-			if (value === 'current') {
-			    return "NOW";
-			} else {
-			    return value;
-			}
-		    }
-		},
-		{
-		    text: gettext('RAM'),
-		    align: 'center',
-		    resizable: false,
-		    dataIndex: 'vmstate',
-		    width: 50,
-		    renderer: function(value, metaData, record) {
-			if (record.data.name !== 'current') {
-			    return Proxmox.Utils.format_boolean(value);
-			}
-		    }
-		},
-		{
-		    text: gettext('Date') + "/" + gettext("Status"),
-		    dataIndex: 'snaptime',
-		    width: 150,
-		    renderer: function(value, metaData, record) {
-			if (record.data.snapstate) {
-			    return record.data.snapstate;
-			}
-			if (value) {
-			    return Ext.Date.format(value,'Y-m-d H:i:s');
-			}
-		    }
-		},
-		{ 
-		    text: gettext('Description'),
-		    dataIndex: 'description',
-		    flex: 1,
-		    renderer: function(value, metaData, record) {
-			if (record.data.name === 'current') {
-			    return gettext("You are here!");
-			} else {
-			    return Ext.String.htmlEncode(value);
-			}
-		    }
-		}
-	    ],
-	    columnLines: true, // will work in 4.1?
-	    listeners: {
-		activate: me.reload,
-		destroy: me.load_task.cancel,
-		itemdblclick: run_editor
-	    }
-	});
-
-	me.callParent();
-
-	me.store.sorters.add(new Ext.util.Sorter({
-	    sorterFn: me.sorterFn
-	}));
-    }
-});
-
diff --git a/www/manager6/tree/SnapshotTree.js b/www/manager6/tree/SnapshotTree.js
new file mode 100644
index 00000000..d4007efa
--- /dev/null
+++ b/www/manager6/tree/SnapshotTree.js
@@ -0,0 +1,361 @@
+Ext.define('PVE.guest.SnapshotTree', {
+    extend: 'Ext.tree.Panel',
+    xtype: 'pveGuestSnapshotTree',
+
+    stateful: true,
+    stateId: 'grid-snapshots',
+
+    viewModel: {
+	data: {
+	    // should be 'qemu' or 'lxc'
+	    type: undefined,
+	    nodename: undefined,
+	    vmid: undefined,
+	    snapshotAllowed: false,
+	    rollbackAllowed: false,
+	    snapshotFeature: false,
+	    selected: '',
+	    load_delay: 3000,
+	},
+	formulas: {
+	    canSnapshot: function(get) {
+		return get('snapshotAllowed') && get('snapshotFeature');
+	    },
+	    canRollback: function(get) {
+		return get('rollbackAllowed')  &&
+		    get('selected') && get('selected') !== 'current';
+	    },
+	    canRemove: function(get) {
+		return get('snapshotAllowed') &&
+		    get('selected') && get('selected') !== 'current';
+	    },
+	    isSnapshot: function(get) {
+		return get('selected') && get('selected') !== 'current';
+	    },
+	    buttonText: function(get) {
+		return get('snapshotAllowed') ? gettext('Edit') : gettext('View');
+	    },
+	    showMemory: function(get) {
+		return get('type') === 'qemu';
+	    },
+	},
+    },
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	newSnapshot: function() {
+	    this.run_editor(false);
+	},
+
+	editSnapshot: function() {
+	    this.run_editor(true);
+	},
+
+	run_editor: function(edit) {
+	    let me = this;
+	    let vm = me.getViewModel();
+	    let snapname;
+	    if (edit) {
+		snapname = vm.get('selected');
+		if (!snapname || snapname === 'current') { return; }
+	    }
+	    let win = Ext.create('PVE.window.Snapshot', {
+		nodename: vm.get('nodename'),
+		vmid: vm.get('vmid'),
+		viewonly: !vm.get('snapshotAllowed'),
+		type: vm.get('type'),
+		isCreate: !edit,
+		submitText: !edit ? gettext('Take Snapshot') : undefined,
+		snapname: snapname,
+	    });
+	    win.show();
+	    me.mon(win, 'destroy', me.reload, me);
+	},
+
+	snapshotAction: function(action, method) {
+	    let me = this;
+	    let view = me.getView();
+	    let vm = me.getViewModel();
+	    let snapname = vm.get('selected');
+	    if (!snapname) { return; }
+
+	    let nodename = vm.get('nodename');
+	    let type = vm.get('type');
+	    let vmid = vm.get('vmid');
+
+	    Proxmox.Utils.API2Request({
+		url: `/nodes/${nodename}/${type}/${vmid}/snapshot/${snapname}/${action}`,
+		method: method,
+		waitMsgTarget: view,
+		callback: function() {
+		    me.reload();
+		},
+		failure: function (response, opts) {
+		    Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+		},
+		success: function(response, options) {
+		    var upid = response.result.data;
+		    var win = Ext.create('Proxmox.window.TaskProgress', { upid: upid });
+		    win.show();
+		}
+	    });
+	},
+
+	rollback: function() { this.snapshotAction('rollback', 'POST'); },
+	remove: function() { this.snapshotAction('', 'DELETE'); },
+
+	cancel: function() {
+	    this.load_task.cancel();
+	},
+
+	reload: function() {
+	    let me = this;
+	    let view = me.getView();
+	    let vm = me.getViewModel();
+	    let nodename = vm.get('nodename');
+	    let vmid = vm.get('vmid');
+	    let type = vm.get('type');
+	    let load_delay = vm.get('load_delay');
+
+	    Proxmox.Utils.API2Request({
+		url: `/nodes/${nodename}/${type}/${vmid}/snapshot`,
+		method: 'GET',
+		failure: function(response, opts) {
+		    Proxmox.Utils.setErrorMask(view, response.htmlStatus);
+		    me.load_task.delay(load_delay);
+		},
+		success: function(response, opts) {
+		    Proxmox.Utils.setErrorMask(view, false);
+		    var digest = 'invalid';
+		    var idhash = {};
+		    var root = { name: '__root', expanded: true, children: [] };
+		    Ext.Array.each(response.result.data, function(item) {
+			item.leaf = true;
+			item.children = [];
+			if (item.name === 'current') {
+			    digest = item.digest + item.running;
+			    item.iconCls = PVE.Utils.get_object_icon_class(vm.get('type'), item);
+			} else {
+			    item.iconCls = 'fa fa-fw fa-history x-fa-tree';
+			}
+			idhash[item.name] = item;
+		    });
+
+		    if (digest !== me.old_digest) {
+			me.old_digest = digest;
+
+			Ext.Array.each(response.result.data, function(item) {
+			    if (item.parent && idhash[item.parent]) {
+				var parent_item = idhash[item.parent];
+				parent_item.children.push(item);
+				parent_item.leaf = false;
+				parent_item.expanded = true;
+				parent_item.expandable = false;
+			    } else {
+				root.children.push(item);
+			    }
+			});
+
+			me.getView().setRootNode(root);
+		    }
+
+		    me.load_task.delay(load_delay);
+		}
+	    });
+
+	    // if we do not have the permissions, we don't have to check
+	    // if we can create a snapshot, since the butten stays disabled
+	    if (!vm.get('snapshotAllowed')) {
+		return;
+	    }
+
+	    Proxmox.Utils.API2Request({
+		url: `/nodes/${nodename}/${type}/${vmid}/feature`,
+		params: { feature: 'snapshot' },
+		method: 'GET',
+		success: function(response, options) {
+		    var res = response.result.data; vm.set('snapshotFeature', !!res.hasFeature); }
+	    });
+	},
+
+	select: function(grid, val) {
+	    let vm = this.getViewModel();
+	    if (val.length < 1) {
+		vm.set('selected', '');
+		return;
+	    }
+	    vm.set('selected', val[0].data.name);
+	},
+
+	init: function(view) {
+	    let me = this;
+	    let vm = me.getViewModel();
+	    me.load_task = new Ext.util.DelayedTask(me.reload, me);
+
+	    if (!view.type) {
+		throw 'guest type not set';
+	    }
+	    vm.set('type', view.type);
+
+	    if (!view.pveSelNode.data.node) {
+		throw "no node name specified";
+	    }
+	    vm.set('nodename', view.pveSelNode.data.node);
+
+	    if (!view.pveSelNode.data.vmid) {
+		throw "no VM ID specified";
+	    }
+	    vm.set('vmid', view.pveSelNode.data.vmid);
+
+	    let caps = Ext.state.Manager.get('GuiCap');
+	    vm.set('snapshotAllowed', !!caps.vms['VM.Snapshot']);
+	    vm.set('rollbackAllowed', !!caps.vms['VM.Snapshot.Rollback']);
+
+	    view.getStore().sorters.add({
+		property: 'order',
+		direction: 'ASC',
+	    });
+
+	    me.reload();
+	},
+    },
+
+    listeners: {
+	selectionchange: 'select',
+	itemdblclick: 'editSnapshot',
+	destroy: 'cancel',
+    },
+
+    layout: 'fit',
+    rootVisible: false,
+    animate: false,
+    sortableColumns: false,
+
+    tbar: [
+	{
+	    xtype: 'proxmoxButton',
+	    text: gettext('Take Snapshot'),
+	    disabled: true,
+	    bind: {
+		disabled: "{!canSnapshot}",
+	    },
+	    handler: 'newSnapshot',
+	},
+	{
+	    xtype: 'proxmoxButton',
+	    text: gettext('Rollback'),
+	    disabled: true,
+	    bind: {
+		disabled: '{!canRollback}',
+	    },
+	    confirmMsg: function() {
+		let view = this.up('treepanel');
+		let rec = view.getSelection()[0];
+		let vmid = view.getViewModel().get('vmid');
+		return Proxmox.Utils.format_task_description('qmrollback', vmid) +
+		    " '" +  rec.data.name + "'";
+	    },
+	    handler: 'rollback',
+	},
+	{
+	    xtype: 'proxmoxButton',
+	    text: gettext('Remove'),
+	    disabled: true,
+	    bind: {
+		disabled: '{!canRemove}',
+	    },
+	    confirmMsg: function() {
+		let view = this.up('treepanel');
+		let rec = view.getSelection()[0];
+		return Ext.String.format(
+		    gettext('Are you sure you want to remove entry {0}'),
+		    `'${rec.data.name}'`
+		);
+	    },
+	    handler: 'remove',
+	},
+	{
+	    xtype: 'proxmoxButton',
+	    text: gettext('Edit'),
+	    bind: {
+		text: '{buttonText}',
+		disabled: '{!isSnapshot}',
+	    },
+	    disabled: true,
+	    edit: true,
+	    handler: 'editSnapshot',
+	}
+    ],
+
+    columnLines: true,
+
+    fields: [
+	'name', 'description', 'snapstate', 'vmstate', 'running',
+	{ name: 'snaptime', type: 'date', dateFormat: 'timestamp' },
+	{
+	    name: 'order',
+	    calculate: function(data) {
+		return data.snaptime || (data.name === 'current' ? 'ZZZ' : data.snapstate);
+	    }
+	}
+    ],
+
+    columns: [
+	{
+	    xtype: 'treecolumn',
+	    text: gettext('Name'),
+	    dataIndex: 'name',
+	    width: 200,
+	    renderer: function(value, metaData, record) {
+		if (value === 'current') {
+		    return gettext('NOW');
+		} else {
+		    return value;
+		}
+	    }
+	},
+	{
+	    text: gettext('RAM'),
+	    hidden: true,
+	    bind: {
+		hidden: '{!showMemory}',
+	    },
+	    align: 'center',
+	    resizable: false,
+	    dataIndex: 'vmstate',
+	    width: 50,
+	    renderer: function(value, metaData, record) {
+		if (record.data.name !== 'current') {
+		    return Proxmox.Utils.format_boolean(value);
+		}
+	    }
+	},
+	{
+	    text: gettext('Date') + "/" + gettext("Status"),
+	    dataIndex: 'snaptime',
+	    width: 150,
+	    renderer: function(value, metaData, record) {
+		if (record.data.snapstate) {
+		    return record.data.snapstate;
+		}
+		if (value) {
+		    return Ext.Date.format(value,'Y-m-d H:i:s');
+		}
+	    }
+	},
+	{
+	    text: gettext('Description'),
+	    dataIndex: 'description',
+	    flex: 1,
+	    renderer: function(value, metaData, record) {
+		if (record.data.name === 'current') {
+		    return gettext("You are here!");
+		} else {
+		    return Ext.String.htmlEncode(value);
+		}
+	    }
+	}
+    ],
+
+});
-- 
2.20.1




More information about the pve-devel mailing list