public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pbs-devel] [PATCH proxmox-backup 0/4] ui for group-filters
@ 2021-11-22 14:20 Dominik Csapak
  2021-11-22 14:20 ` [pbs-devel] [PATCH proxmox-backup 1/4] ui: add GroupSelector Dominik Csapak
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Dominik Csapak @ 2021-11-22 14:20 UTC (permalink / raw)
  To: pbs-devel

adds the ui for group filters for tape-backup-jobs and sync-jobs
some labels are a bit rough ('Type'/'Options') but had no better ones
at the moment

also one small problem, if one has many group filters for specific
groups, a datastore change triggers an api load for every one of them
(since thats how our normal selectors work)

if that is not acceptable, i'll find a solution on how we can only
update it once and send a v2

Dominik Csapak (4):
  ui: add GroupSelector
  ui: add GroupFilter form field(container)
  ui: tape/BackupJobEdit: add second tab with group filters
  ui: SyncJobEdit: add second tab with group filters

 www/Makefile                     |   2 +
 www/css/ext6-pbs.css             |   5 +
 www/form/GroupFilter.js          | 334 +++++++++++++++++++++++++++++++
 www/form/GroupSelector.js        |  83 ++++++++
 www/tape/window/TapeBackupJob.js | 231 +++++++++++----------
 www/window/SyncJobEdit.js        | 242 ++++++++++++----------
 6 files changed, 691 insertions(+), 206 deletions(-)
 create mode 100644 www/form/GroupFilter.js
 create mode 100644 www/form/GroupSelector.js

-- 
2.30.2





^ permalink raw reply	[flat|nested] 6+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 1/4] ui: add GroupSelector
  2021-11-22 14:20 [pbs-devel] [PATCH proxmox-backup 0/4] ui for group-filters Dominik Csapak
@ 2021-11-22 14:20 ` Dominik Csapak
  2021-11-22 14:20 ` [pbs-devel] [PATCH proxmox-backup 2/4] ui: add GroupFilter form field(container) Dominik Csapak
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Dominik Csapak @ 2021-11-22 14:20 UTC (permalink / raw)
  To: pbs-devel

to select either a local or remote group from a datastore

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/Makefile              |  1 +
 www/form/GroupSelector.js | 83 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 84 insertions(+)
 create mode 100644 www/form/GroupSelector.js

diff --git a/www/Makefile b/www/Makefile
index 616c3e12..c975387c 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -42,6 +42,7 @@ JSSRC=							\
 	form/DataStoreSelector.js			\
 	form/CalendarEvent.js				\
 	form/PermissionPathSelector.js			\
+	form/GroupSelector.js				\
 	data/RunningTasksStore.js			\
 	button/TaskButton.js				\
 	config/UserView.js				\
diff --git a/www/form/GroupSelector.js b/www/form/GroupSelector.js
new file mode 100644
index 00000000..1e6c1bd4
--- /dev/null
+++ b/www/form/GroupSelector.js
@@ -0,0 +1,83 @@
+Ext.define('pbs-groups', {
+    extend: 'Ext.data.Model',
+    fields: [
+	'backup-type',
+	'backup-id',
+	{
+	    name: 'group',
+	    type: 'string',
+	    convert: function(value, record) {
+		if (record.data['backup-type'] && record.data['backup-id']) {
+		    return `${record.data['backup-type']}/${record.data['backup-id']}`;
+		}
+		return value;
+	    },
+	},
+    ],
+    proxy: {
+	type: 'proxmox',
+    },
+});
+
+Ext.define('PBS.form.GroupSelector', {
+    extend: 'Proxmox.form.ComboGrid',
+    alias: 'widget.pbsGroupSelector',
+
+    allowBlank: false,
+    autoSelect: false,
+    notFoundIsValid: true,
+    editable: true,
+    valueField: 'group',
+    displayField: 'group',
+
+    reload: function() {
+	let me = this;
+	if (!me.isDisabled()) {
+	    let value = me.getValue();
+	    me.getStore().load({
+		callback: function() {
+		    me.setValue(value);
+		},
+	    });
+	}
+    },
+
+    setLocalDatastore: function(datastore) {
+	let me = this;
+	me.getStore().getProxy().setUrl(`/api2/json/admin/datastore/${datastore}/groups`);
+	me.reload();
+    },
+
+    setRemoteDatastore: function(remote, datastore) {
+	let me = this;
+	me.getStore().getProxy().setUrl(`/api2/json/config/remote/${remote}/scan/${datastore}/groups`);
+	me.reload();
+    },
+
+    store: {
+	sorters: 'group',
+	model: 'pbs-groups',
+    },
+
+    listConfig: {
+	columns: [
+	    {
+		header: gettext('Group'),
+		sortable: true,
+		dataIndex: 'group',
+		renderer: Ext.String.htmlEncode,
+		flex: 1,
+	    },
+	],
+    },
+
+    initComponent: function() {
+	let me = this;
+	me.callParent();
+	if (me.remote && me.datastore) {
+	    me.setRemoteDatastore(me.remote, me.datastore);
+	} else if (me.datastore) {
+	    me.setLocalDatastore(me.datastore);
+	}
+    },
+});
-- 
2.30.2





^ permalink raw reply	[flat|nested] 6+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 2/4] ui: add GroupFilter form field(container)
  2021-11-22 14:20 [pbs-devel] [PATCH proxmox-backup 0/4] ui for group-filters Dominik Csapak
  2021-11-22 14:20 ` [pbs-devel] [PATCH proxmox-backup 1/4] ui: add GroupSelector Dominik Csapak
@ 2021-11-22 14:20 ` Dominik Csapak
  2021-11-22 14:20 ` [pbs-devel] [PATCH proxmox-backup 3/4] ui: tape/BackupJobEdit: add second tab with group filters Dominik Csapak
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Dominik Csapak @ 2021-11-22 14:20 UTC (permalink / raw)
  To: pbs-devel

this contains a grid + button + hidden field which lets the user
add group filters one by one. the first column is the type selector
(type, group, regex) and the second column shows the relevant
input field (groupselector, kvcombobox for type, and textfield for regex)

i had to hack a little to get access to the widgets of the
fieldcontainer, since we cannot simply access the widget of a column
from another column (which we need to show the correct one when changing
the type), also we cannot traverse the widget hirachy in the usual way,
since extjs seems to build it differently for widgetcolumns.

to solve this, i added references of the widgets to the record, and a
reference of the record to the widgets. since this is now a cyclic
reference, i solve that in 'removeFilter' and in 'beforedestroy' of the grid
by removing the references again

also contains a small css style to remove the padding in the rows

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/Makefile            |   1 +
 www/css/ext6-pbs.css    |   5 +
 www/form/GroupFilter.js | 334 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 340 insertions(+)
 create mode 100644 www/form/GroupFilter.js

diff --git a/www/Makefile b/www/Makefile
index c975387c..455fbeec 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -43,6 +43,7 @@ JSSRC=							\
 	form/CalendarEvent.js				\
 	form/PermissionPathSelector.js			\
 	form/GroupSelector.js				\
+	form/GroupFilter.js				\
 	data/RunningTasksStore.js			\
 	button/TaskButton.js				\
 	config/UserView.js				\
diff --git a/www/css/ext6-pbs.css b/www/css/ext6-pbs.css
index 88095dd8..f283bf0b 100644
--- a/www/css/ext6-pbs.css
+++ b/www/css/ext6-pbs.css
@@ -280,3 +280,8 @@ span.snapshot-comment-column {
 .info-pointer div.right-aligned {
     cursor: pointer;
 }
+
+.x-fieldcontainer-default-cell > .x-grid-cell-inner {
+    padding-top: 0px;
+    padding-bottom: 0px;
+}
diff --git a/www/form/GroupFilter.js b/www/form/GroupFilter.js
new file mode 100644
index 00000000..b36960c7
--- /dev/null
+++ b/www/form/GroupFilter.js
@@ -0,0 +1,334 @@
+Ext.define('PBS.form.GroupFilter', {
+    extend: 'Ext.form.FieldContainer',
+    alias: 'widget.pbsGroupFilter',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    cindData: {},
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	removeReferences: function(record) {
+	    for (const widget of Object.keys(record.widgets || {})) {
+		delete record[widget];
+	    }
+
+	    delete record.widgets;
+	},
+
+	cleanupReferences: function(grid) {
+	    let me = this;
+
+	    // break cyclic reference
+	    grid.getStore().each(me.removeReferences);
+	},
+
+	removeFilter: function(field) {
+	    let me = this;
+	    let record = field.getWidgetRecord();
+	    if (record === undefined) {
+		// this is sometimes called before a record/column is initialized
+		return;
+	    }
+
+	    // break cyclic reference
+	    me.removeReferences(record);
+
+	    me.lookup('grid').getStore().remove(record);
+	    me.updateRealField();
+	},
+
+	addFilter: function() {
+	    let me = this;
+	    me.lookup('grid').getStore().add({});
+	    me.updateRealField();
+	},
+
+	onTypeChange: function(field, value) {
+	    let me = this;
+	    let record = field.getWidgetRecord();
+	    if (record === undefined) {
+		return;
+	    }
+
+	    record.set('type', value);
+	    record.commit();
+	    if (record.widgets) {
+		me.setInputValue(record.widgets, record);
+	    }
+	    me.updateRealField();
+	},
+
+	onInputChange: function(field, value) {
+	    let me = this;
+	    if (value === null) {
+		return;
+	    }
+	    let record = field.record;
+	    if (record === undefined) {
+		// this is sometimes called before a record/column is initialized
+		return;
+	    }
+	    record.set('input', value);
+	    record.commit();
+
+	    me.updateRealField();
+	},
+
+	parseGroupFilter: function(filter) {
+	    let [, type, input] = filter.match(/^(type|group|regex):(.*)$/);
+	    return {
+		type,
+		input,
+	    };
+	},
+
+	onValueChange: function(field, values) {
+	    let me = this;
+	    let grid = me.lookup('grid');
+	    if (!values || values.length === 0) {
+		grid.getStore().removeAll();
+		return;
+	    }
+	    let records = values.map((filter) => me.parseGroupFilter(filter));
+	    grid.getStore().setData(records);
+	},
+
+	setInputValue: function(widgets, rec) {
+	    let { type, regex, group } = widgets;
+
+	    type.setHidden(true);
+	    type.setDisabled(true);
+	    type.setValue(undefined);
+
+	    regex.setHidden(true);
+	    regex.setDisabled(true);
+	    regex.setValue(undefined);
+
+	    group.setHidden(true);
+	    group.setDisabled(true);
+	    group.setValue(undefined);
+
+	    let field;
+	    if (rec.data.type === 'type') {
+		field = type;
+	    } else if (rec.data.type === 'regex') {
+		field = regex;
+	    } else if (rec.data.type === 'group') {
+		field = group;
+	    } else {
+		return;
+	    }
+
+	    field.setHidden(false);
+	    field.setDisabled(false);
+	    field.setValue(rec.data.input);
+	},
+
+	newInputColumn: function(col, widget, rec) {
+	    let me = this;
+
+	    let type = widget.down('pbsGroupTypeSelector');
+	    let regex = widget.down('textfield[type=regex]');
+	    let group = widget.down('pbsGroupSelector');
+
+	    // add a widget reference to the record so we can acces them
+	    // from the other column
+	    rec.widgets = {
+		type,
+		regex,
+		group,
+	    };
+
+	    // add a record reference so we can access the record from
+	    // the change handler
+	    type.record = rec;
+	    regex.record = rec;
+	    group.record = rec;
+
+	    // CAUTION we just created a cyclic reference, we have to delete
+	    // that on filter removal!
+
+	    me.setInputValue(rec.widgets, rec);
+	},
+
+	updateRealField: function() {
+	    let me = this;
+
+	    let filter = [];
+	    me.lookup('grid').getStore().each((rec) => {
+		if (rec.data.type && rec.data.input) {
+		    filter.push(`${rec.data.type}:${rec.data.input}`);
+		}
+	    });
+
+	    let field = me.lookup('realfield');
+	    field.suspendEvent('change');
+	    field.setValue(filter);
+	    field.resumeEvent('change');
+	},
+
+	control: {
+	    'grid pbsGroupFilterTypeSelector': {
+		change: 'onTypeChange',
+	    },
+	    'grid fieldcontainer field': {
+		change: 'onInputChange',
+	    },
+	    'grid button': {
+		click: 'removeFilter',
+	    },
+	    'field[reference=realfield]': {
+		change: 'onValueChange',
+	    },
+	    'grid': {
+		beforedestroy: 'cleanupReferences',
+	    },
+	},
+    },
+
+    updateGroupSelectors: function() {
+	let me = this;
+	me.query('pbsGroupSelector').forEach((selector) => {
+	    if (me.remote) {
+		selector.setRemoteDatastore(me.remote, me.datastore);
+	    } else if (me.datastore) {
+		selector.setLocalDatastore(me.datastore);
+	    }
+	});
+    },
+
+    setLocalDatastore: function(datastore) {
+	let me = this;
+	me.remote = undefined;
+	me.datastore = datastore;
+	me.updateGroupSelectors();
+    },
+
+    setRemoteDatastore: function(remote, datastore) {
+	let me = this;
+	me.remote = remote;
+	me.datastore = datastore;
+	me.updateGroupSelectors();
+    },
+
+    items: [
+	{
+	    xtype: 'grid',
+	    reference: 'grid',
+	    margin: '0 0 5 0',
+	    scrollable: true,
+	    height: 300,
+	    store: {
+		fields: ['type', 'input'],
+	    },
+	    emptyText: gettext('Include all groups'),
+	    viewConfig: {
+		deferEmptyText: false,
+	    },
+	    columns: [
+		{
+		    text: gettext('Type'),
+		    xtype: 'widgetcolumn',
+		    dataIndex: 'type',
+		    flex: 1,
+		    widget: {
+			xtype: 'pbsGroupFilterTypeSelector',
+			isFormField: false,
+		    },
+		},
+		{
+		    text: gettext('Filter'),
+		    xtype: 'widgetcolumn',
+		    flex: 1,
+		    onWidgetAttach: 'newInputColumn',
+		    widget: {
+			padding: 0,
+			bodyPadding: 0,
+			xtype: 'fieldcontainer',
+			layout: 'fit',
+			defaults: {
+			    margin: 0,
+			},
+			items: [
+			    {
+				hidden: true,
+				xtype: 'pbsGroupTypeSelector',
+				isFormField: false,
+			    },
+			    {
+				hidden: true,
+				xtype: 'textfield',
+				type: 'regex',
+				isFormField: false,
+			    },
+			    {
+				hidden: true,
+				xtype: 'pbsGroupSelector',
+				isFormField: false,
+			    },
+			],
+		    },
+		},
+		{
+		    xtype: 'widgetcolumn',
+		    width: 40,
+		    widget: {
+			xtype: 'button',
+			iconCls: 'fa fa-trash-o',
+		    },
+		},
+	    ],
+	},
+	{
+	    xtype: 'hiddenfield',
+	    reference: 'realfield',
+	    setValue: function(value) {
+		let me = this;
+		me.value = value;
+		me.checkChange();
+	    },
+	    getValue: function() {
+		return this.value;
+	    },
+	    getSubmitValue: function() {
+		return this.value;
+	    },
+	    cbind: {
+		name: '{name}',
+	    },
+	},
+	{
+	    xtype: 'button',
+	    text: gettext('Add'),
+	    iconCls: 'fa fa-plus-circle',
+	    handler: 'addFilter',
+	},
+    ],
+});
+
+Ext.define('PBS.form.GroupFilterTypeSelector', {
+    extend: 'Proxmox.form.KVComboBox',
+    alias: 'widget.pbsGroupFilterTypeSelector',
+
+    allowBlank: false,
+
+    comboItems: [
+	['type', gettext('Group Type')],
+	['group', gettext('Group Selection')],
+	['regex', gettext('Regex')],
+    ],
+});
+
+Ext.define('PBS.form.GroupTypeSelector', {
+    extend: 'Proxmox.form.KVComboBox',
+    alias: 'widget.pbsGroupTypeSelector',
+
+    allowBlank: false,
+
+    comboItems: [
+	['vm', gettext('VM')],
+	['ct', gettext('CT')],
+	['host', gettext('Host')],
+    ],
+});
-- 
2.30.2





^ permalink raw reply	[flat|nested] 6+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 3/4] ui: tape/BackupJobEdit: add second tab with group filters
  2021-11-22 14:20 [pbs-devel] [PATCH proxmox-backup 0/4] ui for group-filters Dominik Csapak
  2021-11-22 14:20 ` [pbs-devel] [PATCH proxmox-backup 1/4] ui: add GroupSelector Dominik Csapak
  2021-11-22 14:20 ` [pbs-devel] [PATCH proxmox-backup 2/4] ui: add GroupFilter form field(container) Dominik Csapak
@ 2021-11-22 14:20 ` Dominik Csapak
  2021-11-22 14:20 ` [pbs-devel] [PATCH proxmox-backup 4/4] ui: SyncJobEdit: " Dominik Csapak
  2021-11-24  9:04 ` [pbs-devel] [PATCH proxmox-backup 0/4] ui for group-filters Fabian Grünbichler
  4 siblings, 0 replies; 6+ messages in thread
From: Dominik Csapak @ 2021-11-22 14:20 UTC (permalink / raw)
  To: pbs-devel

adds a second tab and adapts the styling to our usual one (border/padding)

adds a change listener to the datastore selector to change it on the
group filters

remaining changes are mostly indentation changes

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/tape/window/TapeBackupJob.js | 231 +++++++++++++++++--------------
 1 file changed, 128 insertions(+), 103 deletions(-)

diff --git a/www/tape/window/TapeBackupJob.js b/www/tape/window/TapeBackupJob.js
index 72821115..266360ce 100644
--- a/www/tape/window/TapeBackupJob.js
+++ b/www/tape/window/TapeBackupJob.js
@@ -11,6 +11,8 @@ Ext.define('PBS.TapeManagement.BackupJobEdit', {
 
     fieldDefaults: { labelWidth: 120 },
 
+    bodyPadding: 0,
+
     cbindData: function(initialConfig) {
 	let me = this;
 
@@ -28,117 +30,140 @@ Ext.define('PBS.TapeManagement.BackupJobEdit', {
     },
 
     items: {
-	xtype: 'inputpanel',
-	onGetValues: function(values) {
-	    let me = this;
-
-	    if (values['export-media-set'] && !me.up('pbsTapeBackupJobEdit').isCreate) {
-		Proxmox.Utils.assemble_field_data(values, { "delete": 'eject-media' });
-	    }
-	    PBS.Utils.delete_if_default(values, 'notify-user');
-	    return values;
-	},
-	column1: [
-	    {
-		xtype: 'pmxDisplayEditField',
-		name: 'id',
-		fieldLabel: gettext('Job ID'),
-		renderer: Ext.htmlEncode,
-		allowBlank: false,
-		cbind: {
-		    editable: '{isCreate}',
-		},
-	    },
-	    {
-		xtype: 'pbsDataStoreSelector',
-		fieldLabel: gettext('Local Datastore'),
-		name: 'store',
-	    },
-	    {
-		xtype: 'pbsMediaPoolSelector',
-		fieldLabel: gettext('Media Pool'),
-		name: 'pool',
-	    },
-	    {
-		xtype: 'pbsDriveSelector',
-		fieldLabel: gettext('Drive'),
-		name: 'drive',
-	    },
+	xtype: 'tabpanel',
+	bodyPadding: 10,
+	border: 0,
+	items: [
 	    {
-		xtype: 'pmxUserSelector',
-		name: 'notify-user',
-		fieldLabel: gettext('Notify User'),
-		emptyText: 'root@pam',
-		allowBlank: true,
-		value: null,
-		renderer: Ext.String.htmlEncode,
-	    },
-	],
+		title: gettext('Options'),
+		xtype: 'inputpanel',
+		onGetValues: function(values) {
+		    let me = this;
 
-	column2: [
-	    {
-		fieldLabel: gettext('Schedule'),
-		xtype: 'pbsCalendarEvent',
-		name: 'schedule',
-		emptyText: gettext('none (disabled)'),
-		cbind: {
-		    deleteEmpty: '{!isCreate}',
-		    value: '{scheduleValue}',
-		},
-	    },
-	    {
-		fieldLabel: gettext('Export Media-Set'),
-		xtype: 'proxmoxcheckbox',
-		name: 'export-media-set',
-		cbind: {
-		    deleteEmpty: '{!isCreate}',
+		    if (values['export-media-set'] && !me.up('pbsTapeBackupJobEdit').isCreate) {
+			Proxmox.Utils.assemble_field_data(values, { "delete": 'eject-media' });
+		    }
+		    PBS.Utils.delete_if_default(values, 'notify-user');
+		    return values;
 		},
-		listeners: {
-		    change: function(cb, value) {
-			let me = this;
-			let eject = me.up('window').down('proxmoxcheckbox[name=eject-media]');
-			if (value) {
-			    eject.setValue(false);
-			}
-			eject.setDisabled(!!value);
+		column1: [
+		    {
+			xtype: 'pmxDisplayEditField',
+			name: 'id',
+			fieldLabel: gettext('Job ID'),
+			renderer: Ext.htmlEncode,
+			allowBlank: false,
+			cbind: {
+			    editable: '{isCreate}',
+			},
 		    },
-		},
-	    },
-	    {
-		fieldLabel: gettext('Eject Media'),
-		xtype: 'proxmoxcheckbox',
-		name: 'eject-media',
-		cbind: {
-		    deleteEmpty: '{!isCreate}',
-		},
-	    },
-	    {
-		fieldLabel: gettext('Latest Only'),
-		xtype: 'proxmoxcheckbox',
-		name: 'latest-only',
-		cbind: {
-		    deleteEmpty: '{!isCreate}',
-		},
-	    },
-	],
+		    {
+			xtype: 'pbsDataStoreSelector',
+			fieldLabel: gettext('Local Datastore'),
+			name: 'store',
+			listeners: {
+			    change: function(field, value) {
+				let me = this;
+				me.up('tabpanel').down('pbsGroupFilter').setLocalDatastore(value);
+			    },
+			},
+		    },
+		    {
+			xtype: 'pbsMediaPoolSelector',
+			fieldLabel: gettext('Media Pool'),
+			name: 'pool',
+		    },
+		    {
+			xtype: 'pbsDriveSelector',
+			fieldLabel: gettext('Drive'),
+			name: 'drive',
+		    },
+		    {
+			xtype: 'pmxUserSelector',
+			name: 'notify-user',
+			fieldLabel: gettext('Notify User'),
+			emptyText: 'root@pam',
+			allowBlank: true,
+			value: null,
+			renderer: Ext.String.htmlEncode,
+		    },
+		],
 
-	columnB: [
-	    {
-		fieldLabel: gettext('Backup Groups'),
-		xtype: 'displayfield',
-		name: 'group-filter',
-		renderer: v => v ? Ext.String.htmlEncode(v) : gettext('All'),
-		cbind: {
-		    hidden: '{isCreate}',
-		},
+		column2: [
+		    {
+			fieldLabel: gettext('Schedule'),
+			xtype: 'pbsCalendarEvent',
+			name: 'schedule',
+			emptyText: gettext('none (disabled)'),
+			cbind: {
+			    deleteEmpty: '{!isCreate}',
+			    value: '{scheduleValue}',
+			},
+		    },
+		    {
+			fieldLabel: gettext('Export Media-Set'),
+			xtype: 'proxmoxcheckbox',
+			name: 'export-media-set',
+			cbind: {
+			    deleteEmpty: '{!isCreate}',
+			},
+			listeners: {
+			    change: function(cb, value) {
+				let me = this;
+				let eject = me.up('window').down('proxmoxcheckbox[name=eject-media]');
+				if (value) {
+				    eject.setValue(false);
+				}
+				eject.setDisabled(!!value);
+			    },
+			},
+		    },
+		    {
+			fieldLabel: gettext('Eject Media'),
+			xtype: 'proxmoxcheckbox',
+			name: 'eject-media',
+			cbind: {
+			    deleteEmpty: '{!isCreate}',
+			},
+		    },
+		    {
+			fieldLabel: gettext('Latest Only'),
+			xtype: 'proxmoxcheckbox',
+			name: 'latest-only',
+			cbind: {
+			    deleteEmpty: '{!isCreate}',
+			},
+		    },
+		],
+
+		columnB: [
+		    {
+			fieldLabel: gettext('Comment'),
+			xtype: 'proxmoxtextfield',
+			name: 'comment',
+			cbind: {
+			    deleteEmpty: '{!isCreate}',
+			},
+		    },
+		],
 	    },
 	    {
-		fieldLabel: gettext('Comment'),
-		xtype: 'proxmoxtextfield',
-		name: 'comment',
-		cbind: {
-		    deleteEmpty: '{!isCreate}',
+		xtype: 'inputpanel',
+		onGetValues: function(values) {
+		    PBS.Utils.delete_if_default(values, 'group-filter');
+		    if (Ext.isArray(values['group-filter']) && values['group-filter'].length === 0) {
+			delete values['group-filter'];
+			values.delete = 'group-filter';
+		    }
+		    return values;
 		},
+		title: gettext('Group Filter'),
+		items: [
+		    {
+			xtype: 'pbsGroupFilter',
+			name: 'group-filter',
+		    },
+		],
 	    },
 	],
     },
-- 
2.30.2





^ permalink raw reply	[flat|nested] 6+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 4/4] ui: SyncJobEdit: add second tab with group filters
  2021-11-22 14:20 [pbs-devel] [PATCH proxmox-backup 0/4] ui for group-filters Dominik Csapak
                   ` (2 preceding siblings ...)
  2021-11-22 14:20 ` [pbs-devel] [PATCH proxmox-backup 3/4] ui: tape/BackupJobEdit: add second tab with group filters Dominik Csapak
@ 2021-11-22 14:20 ` Dominik Csapak
  2021-11-24  9:04 ` [pbs-devel] [PATCH proxmox-backup 0/4] ui for group-filters Fabian Grünbichler
  4 siblings, 0 replies; 6+ messages in thread
From: Dominik Csapak @ 2021-11-22 14:20 UTC (permalink / raw)
  To: pbs-devel

adds a second tab and adapts the styling to our usual one (border/padding)

adds a change listener to the remote datastore selector to change the
remote + datastore on the group filters

remaining changes are mostly indentation changes

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/window/SyncJobEdit.js | 242 ++++++++++++++++++++++----------------
 1 file changed, 139 insertions(+), 103 deletions(-)

diff --git a/www/window/SyncJobEdit.js b/www/window/SyncJobEdit.js
index 3437ef23..57c507cc 100644
--- a/www/window/SyncJobEdit.js
+++ b/www/window/SyncJobEdit.js
@@ -98,6 +98,8 @@ Ext.define('PBS.window.SyncJobEdit', {
 
     subject: gettext('SyncJob'),
 
+    bodyPadding: 0,
+
     fieldDefaults: { labelWidth: 120 },
     defaultFocus: 'proxmoxtextfield[name=comment]',
 
@@ -118,117 +120,151 @@ Ext.define('PBS.window.SyncJobEdit', {
     },
 
     items: {
-	xtype: 'inputpanel',
-	onGetValues: function(values) {
-	    let me = this;
-
-	    if (!values.id && me.up('pbsSyncJobEdit').isCreate) {
-		values.id = 's-' + Ext.data.identifier.Uuid.Global.generate().slice(0, 13);
-	    }
-	    if (!me.isCreate) {
-		PBS.Utils.delete_if_default(values, 'rate-in');
-		if (typeof values.delete === 'string') {
-		    values.delete = values.delete.split(',');
-		}
-	    }
-	    return values;
-	},
-	column1: [
-	    {
-		xtype: 'pmxDisplayEditField',
-		fieldLabel: gettext('Local Datastore'),
-		name: 'store',
-		submitValue: true,
-		cbind: {
-		    editable: '{editDatastore}',
-		    value: '{datastore}',
-		},
-		editConfig: {
-		    xtype: 'pbsDataStoreSelector',
-		    allowBlank: false,
-		},
-	    },
-	    {
-		fieldLabel: gettext('Local Owner'),
-		xtype: 'pbsAuthidSelector',
-		name: 'owner',
-		cbind: {
-		    value: '{authid}',
-		    deleteEmpty: '{!isCreate}',
-		},
-	    },
+	xtype: 'tabpanel',
+	bodyPadding: 10,
+	border: 0,
+	items: [
 	    {
-		fieldLabel: gettext('Remove vanished'),
-		xtype: 'proxmoxcheckbox',
-		name: 'remove-vanished',
-		autoEl: {
-		    tag: 'div',
-		    'data-qtip': gettext('Remove snapshots from local datastore if they vanished from source datastore?'),
+		title: 'Options',
+		xtype: 'inputpanel',
+		onGetValues: function(values) {
+		    let me = this;
+
+		    if (!values.id && me.up('pbsSyncJobEdit').isCreate) {
+			values.id = 's-' + Ext.data.identifier.Uuid.Global.generate().slice(0, 13);
+		    }
+		    if (!me.isCreate) {
+			PBS.Utils.delete_if_default(values, 'rate-in');
+			if (typeof values.delete === 'string') {
+			    values.delete = values.delete.split(',');
+			}
+		    }
+		    return values;
 		},
-		uncheckedValue: false,
-		value: false,
-	    },
-	],
+		column1: [
+		    {
+			xtype: 'pmxDisplayEditField',
+			fieldLabel: gettext('Local Datastore'),
+			name: 'store',
+			submitValue: true,
+			cbind: {
+			    editable: '{editDatastore}',
+			    value: '{datastore}',
+			},
+			editConfig: {
+			    xtype: 'pbsDataStoreSelector',
+			    allowBlank: false,
+			},
+		    },
+		    {
+			fieldLabel: gettext('Local Owner'),
+			xtype: 'pbsAuthidSelector',
+			name: 'owner',
+			cbind: {
+			    value: '{authid}',
+			    deleteEmpty: '{!isCreate}',
+			},
+		    },
+		    {
+			fieldLabel: gettext('Remove vanished'),
+			xtype: 'proxmoxcheckbox',
+			name: 'remove-vanished',
+			autoEl: {
+			    tag: 'div',
+			    'data-qtip': gettext('Remove snapshots from local datastore if they vanished from source datastore?'),
+			},
+			uncheckedValue: false,
+			value: false,
+		    },
+		],
 
-	column2: [
-	    {
-		fieldLabel: gettext('Source Remote'),
-		xtype: 'pbsRemoteSelector',
-		allowBlank: false,
-		name: 'remote',
-		listeners: {
-		    change: function(f, value) {
-			let me = this;
-			let remoteStoreField = me.up('pbsSyncJobEdit').down('field[name=remote-store]');
-			remoteStoreField.setRemote(value);
+		column2: [
+		    {
+			fieldLabel: gettext('Source Remote'),
+			xtype: 'pbsRemoteSelector',
+			allowBlank: false,
+			name: 'remote',
+			listeners: {
+			    change: function(f, value) {
+				let me = this;
+				let remoteStoreField = me.up('pbsSyncJobEdit').down('field[name=remote-store]');
+				remoteStoreField.setRemote(value);
+			    },
+			},
 		    },
-		},
-	    },
-	    {
-		fieldLabel: gettext('Source Datastore'),
-		xtype: 'pbsRemoteStoreSelector',
-		allowBlank: false,
-		autoSelect: false,
-		name: 'remote-store',
-		disabled: true,
-	    },
-	    {
-		fieldLabel: gettext('Sync Schedule'),
-		xtype: 'pbsCalendarEvent',
-		name: 'schedule',
-		emptyText: gettext('none (disabled)'),
-		cbind: {
-		    deleteEmpty: '{!isCreate}',
-		    value: '{scheduleValue}',
-		},
-	    },
-	    {
-		xtype: 'pmxBandwidthField',
-		name: 'rate-in',
-		fieldLabel: gettext('Rate Limit'),
-		emptyText: gettext('Unlimited'),
-		submitAutoScaledSizeUnit: true,
-		// NOTE: handle deleteEmpty in onGetValues due to bandwidth field having a cbind too
-	    },
-	],
+		    {
+			fieldLabel: gettext('Source Datastore'),
+			xtype: 'pbsRemoteStoreSelector',
+			allowBlank: false,
+			autoSelect: false,
+			name: 'remote-store',
+			disabled: true,
+			listeners: {
+			    change: function(field, value) {
+				let me = this;
+				let remoteField = me.up('pbsSyncJobEdit').down('field[name=remote]');
+				let remote = remoteField.getValue();
+				me.up('tabpanel').down('pbsGroupFilter').setRemoteDatastore(remote, value);
+			    },
+			},
+		    },
+		    {
+			fieldLabel: gettext('Sync Schedule'),
+			xtype: 'pbsCalendarEvent',
+			name: 'schedule',
+			emptyText: gettext('none (disabled)'),
+			cbind: {
+			    deleteEmpty: '{!isCreate}',
+			    value: '{scheduleValue}',
+			},
+		    },
+		    {
+			xtype: 'pmxBandwidthField',
+			name: 'rate-in',
+			fieldLabel: gettext('Rate Limit'),
+			emptyText: gettext('Unlimited'),
+			submitAutoScaledSizeUnit: true,
+			// NOTE: handle deleteEmpty in onGetValues due to bandwidth field having a cbind too
+		    },
+		],
 
-	columnB: [
-	    {
-		fieldLabel: gettext('Backup Groups'),
-		xtype: 'displayfield',
-		name: 'group-filter',
-		renderer: v => v ? Ext.String.htmlEncode(v) : gettext('All'),
-		cbind: {
-		    hidden: '{isCreate}',
-		},
+		columnB: [
+		    {
+			fieldLabel: gettext('Backup Groups'),
+			xtype: 'displayfield',
+			name: 'group-filter',
+			renderer: v => v ? Ext.String.htmlEncode(v) : gettext('All'),
+			cbind: {
+			    hidden: '{isCreate}',
+			},
+		    },
+		    {
+			fieldLabel: gettext('Comment'),
+			xtype: 'proxmoxtextfield',
+			name: 'comment',
+			cbind: {
+			    deleteEmpty: '{!isCreate}',
+			},
+		    },
+		],
 	    },
 	    {
-		fieldLabel: gettext('Comment'),
-		xtype: 'proxmoxtextfield',
-		name: 'comment',
-		cbind: {
-		    deleteEmpty: '{!isCreate}',
+		xtype: 'inputpanel',
+		onGetValues: function(values) {
+		    PBS.Utils.delete_if_default(values, 'group-filter');
+		    if (Ext.isArray(values['group-filter']) && values['group-filter'].length === 0) {
+			delete values['group-filter'];
+			values.delete = 'group-filter';
+		    }
+		    return values;
 		},
+		title: gettext('Group Filter'),
+		items: [
+		    {
+			xtype: 'pbsGroupFilter',
+			name: 'group-filter',
+		    },
+		],
 	    },
 	],
     },
-- 
2.30.2





^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [pbs-devel] [PATCH proxmox-backup 0/4] ui for group-filters
  2021-11-22 14:20 [pbs-devel] [PATCH proxmox-backup 0/4] ui for group-filters Dominik Csapak
                   ` (3 preceding siblings ...)
  2021-11-22 14:20 ` [pbs-devel] [PATCH proxmox-backup 4/4] ui: SyncJobEdit: " Dominik Csapak
@ 2021-11-24  9:04 ` Fabian Grünbichler
  4 siblings, 0 replies; 6+ messages in thread
From: Fabian Grünbichler @ 2021-11-24  9:04 UTC (permalink / raw)
  To: Proxmox Backup Server development discussion

On November 22, 2021 3:20 pm, Dominik Csapak wrote:
> adds the ui for group filters for tape-backup-jobs and sync-jobs
> some labels are a bit rough ('Type'/'Options') but had no better ones
> at the moment
> 
> also one small problem, if one has many group filters for specific
> groups, a datastore change triggers an api load for every one of them
> (since thats how our normal selectors work)
> 
> if that is not acceptable, i'll find a solution on how we can only
> update it once and send a v2

thanks for working on this! :)

gave this a quick spin, some issues that probably warrant a v2 anyhow:

- in the edit dialogue the old group filter string in the first tab is 
  still there
- opening the 'add new sync job' dialogue twice in a row leads to an 
  uncaught exception and a broken/empty dialogue[0]
- adding a new sync job requires changing the source datastore after 
  initially setting it, else no 'remote groups' scan is happening
- no remote group scan happens when opening the editor for an existing 
  sync job after initially selecting a source datastore (requires a 
  second change to the datastore selector, like when editing)

the latter two mean the selection for remote groups is empty.

I'd suggest 'Filter type' and 'Filter value' for the headings, then the 
filter type selector can have 'type', 'group' (or ID? or group ID?), 
'regex' as values like the API/config file/CLI? I think it's clear 
from the context that a 'type' filter refers to the backup group type..

IMHO ideally we'd only scan once (well, once initially and then once for 
each change of remote or remote datastore) and cache that for ALL the 
group selectors. especially since the list groups API call is not that 
inexpensive at the moment, since it also returns the number of 
snapshots and the file list of the last one, so has to list all the 
snapshot dirs and their contents (and stat the protected file for each 
snapshot as well).. we could make that part optional maybe (add a new 
parameter that skips the snapshot querying and returns backup_count = 0 
and empty files - or make them optional, since we know a client 
requesting this mode must understand the missing fields)? also just 
realized the groups API call doesn't return empty groups, not sure 
whether that's ideal..

0: Uncaught TypeError: Cannot read property 'each' of null
    at ctor.cleanupReferences (proxmox-backup-gui.js:1354)
    at ctor.fire (ext-all.js:22)
    at ctor.dispatch (ext-all.js:22)
    at ctor.dispatch (ext-all.js:22)
    at ctor.b.doFireEvent (ext-all.js:22)
    at ctor.fireEventArgs (ext-all.js:22)
    at ctor.fireEvent (ext-all.js:22)
    at ctor.destroy (ext-all.js:22)
    at ctor.callParent (ext-all.js:22)
    at ctor.<anonymous> (ext-all.js:22)

> 
> Dominik Csapak (4):
>   ui: add GroupSelector
>   ui: add GroupFilter form field(container)
>   ui: tape/BackupJobEdit: add second tab with group filters
>   ui: SyncJobEdit: add second tab with group filters
> 
>  www/Makefile                     |   2 +
>  www/css/ext6-pbs.css             |   5 +
>  www/form/GroupFilter.js          | 334 +++++++++++++++++++++++++++++++
>  www/form/GroupSelector.js        |  83 ++++++++
>  www/tape/window/TapeBackupJob.js | 231 +++++++++++----------
>  www/window/SyncJobEdit.js        | 242 ++++++++++++----------
>  6 files changed, 691 insertions(+), 206 deletions(-)
>  create mode 100644 www/form/GroupFilter.js
>  create mode 100644 www/form/GroupSelector.js
> 
> -- 
> 2.30.2
> 
> 
> 
> _______________________________________________
> pbs-devel mailing list
> pbs-devel@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
> 
> 
> 




^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2021-11-24  9:04 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-11-22 14:20 [pbs-devel] [PATCH proxmox-backup 0/4] ui for group-filters Dominik Csapak
2021-11-22 14:20 ` [pbs-devel] [PATCH proxmox-backup 1/4] ui: add GroupSelector Dominik Csapak
2021-11-22 14:20 ` [pbs-devel] [PATCH proxmox-backup 2/4] ui: add GroupFilter form field(container) Dominik Csapak
2021-11-22 14:20 ` [pbs-devel] [PATCH proxmox-backup 3/4] ui: tape/BackupJobEdit: add second tab with group filters Dominik Csapak
2021-11-22 14:20 ` [pbs-devel] [PATCH proxmox-backup 4/4] ui: SyncJobEdit: " Dominik Csapak
2021-11-24  9:04 ` [pbs-devel] [PATCH proxmox-backup 0/4] ui for group-filters Fabian Grünbichler

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal