[pve-devel] [PATCH manager 7/7] ui: BackupEdit: refactor edit window into declarative style

Dominik Csapak d.csapak at proxmox.com
Mon Mar 6 15:23:35 CET 2023


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 at proxmox.com>
---
 www/manager6/dc/Backup.js | 690 ++++++++++++++++++++------------------
 1 file changed, 361 insertions(+), 329 deletions(-)

diff --git a/www/manager6/dc/Backup.js b/www/manager6/dc/Backup.js
index d5e8bf20c..d0046177a 100644
--- a/www/manager6/dc/Backup.js
+++ b/www/manager6/dc/Backup.js
@@ -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', {
-- 
2.30.2






More information about the pve-devel mailing list