]> git.proxmox.com Git - pve-manager.git/commitdiff
ui: BackupEdit: refactor edit window into declarative style
authorDominik Csapak <d.csapak@proxmox.com>
Mon, 6 Mar 2023 14:23:35 +0000 (15:23 +0100)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Sat, 11 Mar 2023 17:47:34 +0000 (18:47 +0100)
simplifies some things, e.g. en/disabling the grid and pool selector
while refactoring, cleanup up some smaller things like nested if/else
paths, unnecessary splitting of the deprecated 'dow' parameter, etc.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
www/manager6/dc/Backup.js

index d5e8bf20c24b200bb7c5e56d9e7e72520569e63b..d0046177ae0c9ddc7f90cb626d9cbbb63a7b8bd6 100644 (file)
@@ -2,72 +2,107 @@ Ext.define('PVE.dc.BackupEdit', {
     extend: 'Proxmox.window.Edit',
     alias: ['widget.pveDcBackupEdit'],
 
+    mixins: ['Proxmox.Mixin.CBind'],
+
     defaultFocus: undefined,
 
-    initComponent: function() {
-       let me = this;
+    subject: gettext("Backup Job"),
+    bodyPadding: 0,
 
-       me.isCreate = !me.jobid;
+    url: '/api2/extjs/cluster/backup',
+    method: 'POST',
+    isCreate: true,
 
-       let url, method;
-       if (me.isCreate) {
-           url = '/api2/extjs/cluster/backup';
-           method = 'POST';
-       } else {
-           url = '/api2/extjs/cluster/backup/' + me.jobid;
-           method = 'PUT';
+    cbindData: function() {
+       let me = this;
+       if (me.jobid) {
+           me.isCreate = false;
+           me.method = 'PUT';
+           me.url += `/${me.jobid}`;
        }
+       return {};
+    },
 
-       // 'value' can be assigned a string or an array
-       let selModeField = Ext.create('Proxmox.form.KVComboBox', {
-           xtype: 'proxmoxKVComboBox',
-           comboItems: [
-               ['include', gettext('Include selected VMs')],
-               ['all', gettext('All')],
-               ['exclude', gettext('Exclude selected VMs')],
-               ['pool', gettext('Pool based')],
-           ],
-           fieldLabel: gettext('Selection mode'),
-           name: 'selMode',
-           value: '',
-       });
+    controller: {
+       xclass: 'Ext.app.ViewController',
 
+       onGetValues: function(values) {
+           let me = this;
+           let isCreate = me.getView().isCreate;
+           if (!values.node) {
+               if (!isCreate) {
+                   Proxmox.Utils.assemble_field_data(values, { 'delete': 'node' });
+               }
+               delete values.node;
+           }
 
-       let storagesel = Ext.create('PVE.form.StorageSelector', {
-           fieldLabel: gettext('Storage'),
-           clusterView: true,
-           storageContent: 'backup',
-           allowBlank: false,
-           name: 'storage',
-           listeners: {
-               change: function(f, v) {
-                   let store = f.getStore();
-                   let rec = store.findRecord('storage', v, 0, false, true, true);
-                   let compressionSelector = me.down('pveCompressionSelector');
-
-                   if (rec && rec.data && rec.data.type === 'pbs') {
-                       compressionSelector.setValue('zstd');
-                       compressionSelector.setDisabled(true);
-                   } else if (!compressionSelector.getEditable()) {
-                       compressionSelector.setDisabled(false);
-                   }
-               },
-           },
-       });
+           if (!values.id && isCreate) {
+               values.id = 'backup-' + Ext.data.identifier.Uuid.Global.generate().slice(0, 13);
+           }
 
-       let vmgrid = Ext.createWidget('vmselector', {
-           height: 300,
-           name: 'vmid',
-           disabled: true,
-           allowBlank: false,
-           columnSelection: ['vmid', 'node', 'status', 'name', 'type'],
-       });
+           let selMode = values.selMode;
+           delete values.selMode;
+
+           if (selMode === 'all') {
+               values.all = 1;
+               values.exclude = '';
+               delete values.vmid;
+           } else if (selMode === 'exclude') {
+               values.all = 1;
+               values.exclude = values.vmid;
+               delete values.vmid;
+           } else if (selMode === 'pool') {
+               delete values.vmid;
+           }
+
+           if (selMode !== 'pool') {
+               delete values.pool;
+           }
+           return values;
+       },
+
+       nodeChange: function(f, value) {
+           let me = this;
+           me.lookup('storageSelector').setNodename(value);
+           let vmgrid = me.lookup('vmgrid');
+           let store = vmgrid.getStore();
+
+           store.clearFilter();
+           store.filterBy(function(rec) {
+               return !value || rec.get('node') === value;
+           });
+
+           let mode = me.lookup('modeSelector').getValue();
+           if (mode === 'all') {
+               vmgrid.selModel.selectAll(true);
+           }
+           if (mode === 'pool') {
+               me.selectPoolMembers();
+           }
+       },
+
+       storageChange: function(f, v) {
+           let me = this;
+           let rec = f.getStore().findRecord('storage', v, 0, false, true, true);
+           let compressionSelector = me.lookup('compressionSelector');
+
+           if (rec?.data?.type === 'pbs') {
+               compressionSelector.setValue('zstd');
+               compressionSelector.setDisabled(true);
+           } else if (!compressionSelector.getEditable()) {
+               compressionSelector.setDisabled(false);
+           }
+       },
+
+       selectPoolMembers: function() {
+           let me = this;
+           let vmgrid = me.lookup('vmgrid');
+           let poolid = me.lookup('poolSelector').getValue();
 
-       let selectPoolMembers = function(poolid) {
+           vmgrid.getSelectionModel().deselectAll(true);
            if (!poolid) {
                return;
            }
-           vmgrid.selModel.deselectAll(true);
            vmgrid.getStore().filter([
                {
                    id: 'poolFilter',
@@ -76,312 +111,309 @@ Ext.define('PVE.dc.BackupEdit', {
                },
            ]);
            vmgrid.selModel.selectAll(true);
-       };
+       },
 
-       let selPool = Ext.create('PVE.form.PoolSelector', {
-           fieldLabel: gettext('Pool to backup'),
-           hidden: true,
-           allowBlank: true,
-           name: 'pool',
-           listeners: {
-               change: function(selpool, newValue, oldValue) {
-                   selectPoolMembers(newValue);
-               },
-           },
-       });
+       modeChange: function(f, value, oldValue) {
+           let me = this;
+           let vmgrid = me.lookup('vmgrid');
+           vmgrid.getStore().removeFilter('poolFilter');
 
-       let nodesel = Ext.create('PVE.form.NodeSelector', {
-           name: 'node',
-           fieldLabel: gettext('Node'),
-           allowBlank: true,
-           editable: true,
-           autoSelect: false,
-           emptyText: '-- ' + gettext('All') + ' --',
-           listeners: {
-               change: function(f, value) {
-                   storagesel.setNodename(value);
-                   let mode = selModeField.getValue();
-                   let store = vmgrid.getStore();
-                   store.clearFilter();
-                   store.filterBy(function(rec) {
-                       return !value || rec.get('node') === value;
-                   });
-                   if (mode === 'all') {
-                       vmgrid.selModel.selectAll(true);
-                   }
-                   if (mode === 'pool') {
-                       selectPoolMembers(selPool.value);
-                   }
-               },
-           },
-       });
+           if (oldValue === 'all' && value !== 'all') {
+               vmgrid.getSelectionModel().deselectAll(true);
+           }
 
-       let column1 = [
-           nodesel,
-           storagesel,
-           {
-               xtype: 'pveCalendarEvent',
-               fieldLabel: gettext('Schedule'),
-               allowBlank: false,
-               name: 'schedule',
-           },
-           selModeField,
-           selPool,
-       ];
-
-       let column2 = [
-           {
-               xtype: 'textfield',
-               fieldLabel: gettext('Send email to'),
-               name: 'mailto',
-           },
-           {
-               xtype: 'pveEmailNotificationSelector',
-               fieldLabel: gettext('Email'),
-               name: 'mailnotification',
-               deleteEmpty: !me.isCreate,
-               value: me.isCreate ? 'always' : '',
-           },
-           {
-               xtype: 'pveCompressionSelector',
-               fieldLabel: gettext('Compression'),
-               name: 'compress',
-               deleteEmpty: !me.isCreate,
-               value: 'zstd',
-           },
-           {
-               xtype: 'pveBackupModeSelector',
-               fieldLabel: gettext('Mode'),
-               value: 'snapshot',
-               name: 'mode',
-           },
-           {
-               xtype: 'proxmoxcheckbox',
-               fieldLabel: gettext('Enable'),
-               name: 'enabled',
-               uncheckedValue: 0,
-               defaultValue: 1,
-               checked: true,
-           },
-       ];
+           if (value === 'all') {
+               vmgrid.getSelectionModel().selectAll(true);
+           }
 
-       let ipanel = Ext.create('Proxmox.panel.InputPanel', {
-           onlineHelp: 'chapter_vzdump',
-           column1: column1,
-           column2: column2,
-           columnB: [
-               {
-                   xtype: 'proxmoxtextfield',
-                   name: 'comment',
-                   fieldLabel: gettext('Job Comment'),
-                   deleteEmpty: !me.isCreate,
-                   autoEl: {
-                       tag: 'div',
-                       'data-qtip': gettext('Description of the job'),
-                   },
-               },
-               vmgrid,
-           ],
-           advancedColumn1: [
-               {
-                   xtype: 'proxmoxcheckbox',
-                   fieldLabel: gettext('Repeat missed'),
-                   name: 'repeat-missed',
-                   uncheckedValue: 0,
-                   defaultValue: 0,
-                   deleteDefaultValue: !me.isCreate,
-               },
-           ],
-           onGetValues: function(values) {
-               if (!values.node) {
-                   if (!me.isCreate) {
-                       Proxmox.Utils.assemble_field_data(values, { 'delete': 'node' });
-                   }
-                   delete values.node;
-               }
+           if (value === 'pool') {
+               me.selectPoolMembers();
+           }
+       },
 
-               if (!values.id && me.isCreate) {
-                   values.id = 'backup-' + Ext.data.identifier.Uuid.Global.generate().slice(0, 13);
-               }
+       init: function(view) {
+           let me = this;
+           if (view.isCreate) {
+               me.lookup('modeSelector').setValue('include');
+           } else {
+               view.load({
+                   success: function(response, _options) {
+                       let data = response.result.data;
 
-               let selMode = values.selMode;
-               delete values.selMode;
-
-               if (selMode === 'all') {
-                   values.all = 1;
-                   values.exclude = '';
-                   delete values.vmid;
-               } else if (selMode === 'exclude') {
-                   values.all = 1;
-                   values.exclude = values.vmid;
-                   delete values.vmid;
-               } else if (selMode === 'pool') {
-                   delete values.vmid;
-               }
+                       if (data.exclude) {
+                           data.vmid = data.exclude;
+                           data.selMode = 'exclude';
+                       } else if (data.all) {
+                           data.vmid = '';
+                           data.selMode = 'all';
+                       } else if (data.pool) {
+                           data.selMode = 'pool';
+                           data.selPool = data.pool;
+                       } else {
+                           data.selMode = 'include';
+                       }
 
-               if (selMode !== 'pool') {
-                   delete values.pool;
-               }
-               return values;
-           },
-       });
+                       me.getViewModel().set('selMode', data.selMode);
 
-       selModeField.on('change', function(f, value, oldValue) {
-           if (oldValue === 'pool') {
-               vmgrid.getStore().removeFilter('poolFilter');
-           }
+                       if (data['prune-backups']) {
+                           Object.assign(data, data['prune-backups']);
+                           delete data['prune-backups'];
+                       } else if (data.maxfiles !== undefined) {
+                           if (data.maxfiles > 0) {
+                               data['keep-last'] = data.maxfiles;
+                           } else {
+                               data['keep-all'] = 1;
+                           }
+                           delete data.maxfiles;
+                       }
 
-           if (oldValue === 'all' || oldValue === 'pool') {
-               vmgrid.selModel.deselectAll(true);
-           }
+                       if (data['notes-template']) {
+                           data['notes-template'] =
+                               PVE.Utils.unEscapeNotesTemplate(data['notes-template']);
+                       }
 
-           if (value === 'all') {
-               vmgrid.selModel.selectAll(true);
-               vmgrid.setDisabled(true);
-           } else {
-               vmgrid.setDisabled(false);
+                       view.setValues(data);
+                   },
+               });
            }
+       },
+    },
 
-           if (value === 'pool') {
-               vmgrid.setDisabled(true);
-               vmgrid.selModel.deselectAll(true);
-               selPool.setVisible(true);
-               selPool.setDisabled(false);
-               selPool.allowBlank = false;
-               selectPoolMembers(selPool.value);
-           } else {
-               selPool.setVisible(false);
-               selPool.setDisabled(true);
-               selPool.allowBlank = true;
-           }
-       });
+    viewModel: {
+       data: {
+           selMode: 'include',
+       },
 
-       Ext.applyIf(me, {
-           subject: gettext("Backup Job"),
-           url: url,
-           method: method,
-           bodyPadding: 0,
+       formulas: {
+           poolMode: (get) => get('selMode') === 'pool',
+           disableVMSelection: (get) => get('selMode') !== 'include' && get('selMode') !== 'exclude',
+       },
+    },
+
+    items: [
+       {
+           xtype: 'tabpanel',
+           region: 'center',
+           layout: 'fit',
+           bodyPadding: 10,
            items: [
                {
-                   xtype: 'tabpanel',
+                   xtype: 'container',
+                   title: gettext('General'),
                    region: 'center',
-                   layout: 'fit',
-                   bodyPadding: 10,
+                   layout: {
+                       type: 'vbox',
+                       align: 'stretch',
+                   },
                    items: [
-                       {
-                           xtype: 'container',
-                           title: gettext('General'),
-                           region: 'center',
-                           layout: {
-                               type: 'vbox',
-                               align: 'stretch',
-                           },
-                           items: [
-                               ipanel,
-                           ],
-                       },
-                       {
-                           xtype: 'pveBackupJobPrunePanel',
-                           title: gettext('Retention'),
-                           isCreate: me.isCreate,
-                           keepAllDefaultForCreate: false,
-                           showPBSHint: false,
-                           fallbackHintHtml: gettext('Without any keep option, the storage\'s configuration or node\'s vzdump.conf is used as fallback'),
-                       },
                        {
                            xtype: 'inputpanel',
-                           title: gettext('Note Template'),
-                           region: 'center',
-                           layout: {
-                               type: 'vbox',
-                               align: 'stretch',
-                           },
-                           onGetValues: function(values) {
-                               if (values['notes-template']) {
-                                   values['notes-template'] = PVE.Utils.escapeNotesTemplate(
-                                       values['notes-template']);
-                               }
-                               return values;
-                           },
-                           items: [
+                           onlineHelp: 'chapter_vzdump',
+                           column1: [
+                               {
+                                   xtype: 'pveNodeSelector',
+                                   name: 'node',
+                                   fieldLabel: gettext('Node'),
+                                   allowBlank: true,
+                                   editable: true,
+                                   autoSelect: false,
+                                   emptyText: '-- ' + gettext('All') + ' --',
+                                   listeners: {
+                                       change: 'nodeChange',
+                                   },
+                               },
+                               {
+                                   xtype: 'pveStorageSelector',
+                                   reference: 'storageSelector',
+                                   fieldLabel: gettext('Storage'),
+                                   clusterView: true,
+                                   storageContent: 'backup',
+                                   allowBlank: false,
+                                   name: 'storage',
+                                   listeners: {
+                                       change: 'storageChange',
+                                   },
+                               },
+                               {
+                                   xtype: 'pveCalendarEvent',
+                                   fieldLabel: gettext('Schedule'),
+                                   allowBlank: false,
+                                   name: 'schedule',
+                               },
+                               {
+                                   xtype: 'proxmoxKVComboBox',
+                                   reference: 'modeSelector',
+                                   comboItems: [
+                                       ['include', gettext('Include selected VMs')],
+                                       ['all', gettext('All')],
+                                       ['exclude', gettext('Exclude selected VMs')],
+                                       ['pool', gettext('Pool based')],
+                                   ],
+                                   fieldLabel: gettext('Selection mode'),
+                                   name: 'selMode',
+                                   value: '',
+                                   bind: {
+                                       value: '{selMode}',
+                                   },
+                                   listeners: {
+                                       change: 'modeChange',
+                                   },
+                               },
+                               {
+                                   xtype: 'pvePoolSelector',
+                                   reference: 'poolSelector',
+                                   fieldLabel: gettext('Pool to backup'),
+                                   hidden: true,
+                                   allowBlank: false,
+                                   name: 'pool',
+                                   listeners: {
+                                       change: 'selectPoolMembers',
+                                   },
+                                   bind: {
+                                       hidden: '{!poolMode}',
+                                       disabled: '{!poolMode}',
+                                   },
+                               },
+                           ],
+                           column2: [
+                               {
+                                   xtype: 'textfield',
+                                   fieldLabel: gettext('Send email to'),
+                                   name: 'mailto',
+                               },
+                               {
+                                   xtype: 'pveEmailNotificationSelector',
+                                   fieldLabel: gettext('Email'),
+                                   name: 'mailnotification',
+                                   cbind: {
+                                       value: (get) => get('isCreate') ? 'always' : '',
+                                       deleteEmpty: '{!isCreate}',
+                                   },
+                               },
+                               {
+                                   xtype: 'pveCompressionSelector',
+                                   reference: 'compressionSelector',
+                                   fieldLabel: gettext('Compression'),
+                                   name: 'compress',
+                                   cbind: {
+                                       deleteEmpty: '{!isCreate}',
+                                   },
+                                   value: 'zstd',
+                               },
+                               {
+                                   xtype: 'pveBackupModeSelector',
+                                   fieldLabel: gettext('Mode'),
+                                   value: 'snapshot',
+                                   name: 'mode',
+                               },
+                               {
+                                   xtype: 'proxmoxcheckbox',
+                                   fieldLabel: gettext('Enable'),
+                                   name: 'enabled',
+                                   uncheckedValue: 0,
+                                   defaultValue: 1,
+                                   checked: true,
+                               },
+                           ],
+                           columnB: [
+                               {
+                                   xtype: 'proxmoxtextfield',
+                                   name: 'comment',
+                                   fieldLabel: gettext('Job Comment'),
+                                   cbind: {
+                                       deleteEmpty: '{!isCreate}',
+                                   },
+                                   autoEl: {
+                                       tag: 'div',
+                                       'data-qtip': gettext('Description of the job'),
+                                   },
+                               },
                                {
-                                   xtype: 'textarea',
-                                   name: 'notes-template',
-                                   fieldLabel: gettext('Backup Notes'),
-                                    height: 100,
-                                   maxLength: 512,
-                                   deleteEmpty: !me.isCreate,
-                                   value: me.isCreate ? '{{guestname}}' : undefined,
+                                   xtype: 'vmselector',
+                                   reference: 'vmgrid',
+                                   height: 300,
+                                   name: 'vmid',
+                                   disabled: true,
+                                   allowBlank: false,
+                                   columnSelection: ['vmid', 'node', 'status', 'name', 'type'],
+                                   bind: {
+                                       disabled: '{disableVMSelection}',
+                                   },
                                },
+                           ],
+                           advancedColumn1: [
                                {
-                                   xtype: 'box',
-                                   style: {
-                                       margin: '8px 0px',
-                                       'line-height': '1.5em',
+                                   xtype: 'proxmoxcheckbox',
+                                   fieldLabel: gettext('Repeat missed'),
+                                   name: 'repeat-missed',
+                                   uncheckedValue: 0,
+                                   defaultValue: 0,
+                                   cbind: {
+                                       deleteDefaultValue: '{!isCreate}',
                                    },
-                                   html: gettext('The notes are added to each backup created by this job.')
-                                     + '<br>'
-                                     + Ext.String.format(
-                                       gettext('Possible template variables are: {0}'),
-                                       PVE.Utils.notesTemplateVars.map(v => `<code>{{${v}}}</code>`).join(', '),
-                                   ),
                                },
                            ],
+                           onGetValues: function(values) {
+                               return this.up('window').getController().onGetValues(values);
+                           },
                        },
                    ],
                },
-           ],
-
-       });
-
-       me.callParent();
-
-       if (me.isCreate) {
-           selModeField.setValue('include');
-       } else {
-            me.load({
-               success: function(response, options) {
-                   let data = response.result.data;
-
-                   data.dow = (data.dow || '').split(',');
-
-                   if (data.all || data.exclude) {
-                       if (data.exclude) {
-                           data.vmid = data.exclude;
-                           data.selMode = 'exclude';
-                       } else {
-                           data.vmid = '';
-                           data.selMode = 'all';
-                       }
-                   } else if (data.pool) {
-                       data.selMode = 'pool';
-                       data.selPool = data.pool;
-                   } else {
-                       data.selMode = 'include';
-                   }
-
-                   if (data['prune-backups']) {
-                       Object.assign(data, data['prune-backups']);
-                       delete data['prune-backups'];
-                   } else if (data.maxfiles !== undefined) {
-                       if (data.maxfiles > 0) {
-                           data['keep-last'] = data.maxfiles;
-                       } else {
-                           data['keep-all'] = 1;
+               {
+                   xtype: 'pveBackupJobPrunePanel',
+                   title: gettext('Retention'),
+                   cbind: {
+                       isCreate: '{isCreate}',
+                   },
+                   keepAllDefaultForCreate: false,
+                   showPBSHint: false,
+                   fallbackHintHtml: gettext('Without any keep option, the storage\'s configuration or node\'s vzdump.conf is used as fallback'),
+               },
+               {
+                   xtype: 'inputpanel',
+                   title: gettext('Note Template'),
+                   region: 'center',
+                   layout: {
+                       type: 'vbox',
+                       align: 'stretch',
+                   },
+                   onGetValues: function(values) {
+                       if (values['notes-template']) {
+                           values['notes-template'] =
+                               PVE.Utils.escapeNotesTemplate(values['notes-template']);
                        }
-                       delete data.maxfiles;
-                   }
-
-                   if (data['notes-template']) {
-                       data['notes-template'] = PVE.Utils.unEscapeNotesTemplate(
-                           data['notes-template']);
-                   }
-
-                   me.setValues(data);
+                       return values;
+                   },
+                   items: [
+                       {
+                           xtype: 'textarea',
+                           name: 'notes-template',
+                           fieldLabel: gettext('Backup Notes'),
+                           height: 100,
+                           maxLength: 512,
+                           cbind: {
+                               deleteEmpty: '{!isCreate}',
+                               value: (get) => get('isCreate') ? '{{guestname}}' : undefined,
+                           },
+                       },
+                       {
+                           xtype: 'box',
+                           style: {
+                               margin: '8px 0px',
+                               'line-height': '1.5em',
+                           },
+                           html: gettext('The notes are added to each backup created by this job.')
+                               + '<br>'
+                               + Ext.String.format(
+                                   gettext('Possible template variables are: {0}'),
+                                   PVE.Utils.notesTemplateVars.map(v => `<code>{{${v}}}</code>`).join(', '),
+                               ),
+                       },
+                   ],
                },
-           });
-       }
-    },
+           ],
+       },
+    ],
 });
 
 Ext.define('PVE.dc.BackupView', {