]> git.proxmox.com Git - pve-manager.git/commitdiff
gui: refator SnapshotTree
authorDominik Csapak <d.csapak@proxmox.com>
Thu, 30 Jan 2020 15:58:52 +0000 (16:58 +0100)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Thu, 30 Jan 2020 17:01:38 +0000 (18:01 +0100)
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@proxmox.com>
www/manager6/Makefile
www/manager6/lxc/Config.js
www/manager6/lxc/SnapshotTree.js [deleted file]
www/manager6/qemu/Config.js
www/manager6/qemu/SnapshotTree.js [deleted file]
www/manager6/tree/SnapshotTree.js [new file with mode: 0644]

index 3c99ec6c968275266e7ca88dd91a9171d6e5933e..eb7ac0048d4642247a2faf3cbdea1d606d596d83 100644 (file)
@@ -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                                   \
index e14b5ad25ecca1437bc8bc1df4c86290f61a21b2..29464706dc0bef070e3009168d3c98163445dce5 100644 (file)
@@ -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 (file)
index 0c1d9a6..0000000
+++ /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
-       }));
-    }
-});
index 195db3a287057eee22c8760944783be615883594..ea8b613714e524d94399cea20122b29aae4abbcd 100644 (file)
@@ -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 (file)
index a389143..0000000
+++ /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 (file)
index 0000000..d4007ef
--- /dev/null
@@ -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);
+               }
+           }
+       }
+    ],
+
+});