[pve-devel] [PATCH manager 2/3] gui: refator SnapshotTree
Thomas Lamprecht
t.lamprecht at proxmox.com
Thu Jan 30 20:08:51 CET 2020
On 1/30/20 4:58 PM, Dominik Csapak wrote:
> 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
>
> [snip]
> 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';
> + },
opnionated style fix for above, use arror functions and reused isSnapshot formula
in other formulas.
> + },
> + },
> +
> + 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'); },
followed up for above crammed in one line stuff, looks especially weird because
cancel below has also just a one-liner method-body, but is formatted nicely.
> +
> + 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) {
added me.destroyed guard here (see below)
> + Proxmox.Utils.setErrorMask(view, response.htmlStatus);
> + me.load_task.delay(load_delay);
> + },
> + success: function(response, opts) {
added me.destroyed guard here (see below)
> + 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); }
fixed above "mess", sorry but the mix of two statement + closing bracket for
the success callback on the same line really got me wondering what's going on ^^
Also, put a guard against me.destroyed here, which could happen if this callback
got, well, called back once the component was destroyed already - e.g., when the
user navigated to another guest/panel.
Was the simplest thing working I came up with quickly, better ideas are welcomed.
> + });
> + },
> +
> + 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' },
just saw above now, but it's actually something we have here and there, not biggest
fan but OK.
> + {
> + 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);
> + }
> + }
> + }
> + ],
> +
> +});
>
More information about the pve-devel
mailing list