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.csa...@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);
> +             }
> +         }
> +     }
> +    ],
> +
> +});
> 


_______________________________________________
pve-devel mailing list
pve-devel@pve.proxmox.com
https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to