public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pbs-devel] [PATCH proxmox-backup v4 0/5] ui: unify and improve tape restore window
@ 2021-05-21 10:20 Dominik Csapak
  2021-05-21 10:20 ` [pbs-devel] [PATCH proxmox-backup v4 1/5] ui: tape/TapeRestore: fix small DataStoreMappingGrid bugs Dominik Csapak
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Dominik Csapak @ 2021-05-21 10:20 UTC (permalink / raw)
  To: pbs-devel

changes from v3 (sent as v2):
* use two panel layout for restore window ('what', 'where')
* rewrite using a controller
* add some small bugfixes for the snapshotGrid and datastore mapping
* remove reload after restore

changes from v2:
* split patches in proper self-containted commits
* changed layout in restore window
   datastore mapping grid is now on the right, other options on the left
* integrated the selection checkbox into the checkbox selection model of
  the grid (removing a seperate field)
* integrated the filter textbox into the gridfilters plugin, to save space
  (like we do in pves bulk action window)
  the benefit of this is that we do not have to combine/split the
  store:snapshots anymore besides when assembling the submitdata,
  this makes the code a little nicer imho

Dominik Csapak (5):
  ui: tape/TapeRestore: fix small DataStoreMappingGrid bugs
  ui: tape/TapeRestore: improve SnapshotGrid
  ui: tape/window/TapeRestore: enabling selecting multiple snapshots
  ui: tape/BackupOverview: also allow to filter by group for restore
  ui: tape/BackupOverview: do not reload on restore

 www/tape/BackupOverview.js     |  41 +--
 www/tape/window/TapeRestore.js | 563 +++++++++++++++++++++++----------
 2 files changed, 416 insertions(+), 188 deletions(-)

-- 
2.20.1





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

* [pbs-devel] [PATCH proxmox-backup v4 1/5] ui: tape/TapeRestore: fix small DataStoreMappingGrid bugs
  2021-05-21 10:20 [pbs-devel] [PATCH proxmox-backup v4 0/5] ui: unify and improve tape restore window Dominik Csapak
@ 2021-05-21 10:20 ` Dominik Csapak
  2021-05-21 10:20 ` [pbs-devel] [PATCH proxmox-backup v4 2/5] ui: tape/TapeRestore: improve SnapshotGrid Dominik Csapak
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Dominik Csapak @ 2021-05-21 10:20 UTC (permalink / raw)
  To: pbs-devel

enable scrolling by default, and handle the case that getErrors gets
called when the component is not yet rendered

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/tape/window/TapeRestore.js | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/www/tape/window/TapeRestore.js b/www/tape/window/TapeRestore.js
index 9967d9d8..2a876619 100644
--- a/www/tape/window/TapeRestore.js
+++ b/www/tape/window/TapeRestore.js
@@ -203,6 +203,8 @@ Ext.define('PBS.TapeManagement.DataStoreMappingGrid', {
     alias: 'widget.pbsDataStoreMappingField',
     mixins: ['Ext.form.field.Field'],
 
+    scrollable: true,
+
     getValue: function() {
 	let me = this;
 	let datastores = [];
@@ -246,15 +248,20 @@ Ext.define('PBS.TapeManagement.DataStoreMappingGrid', {
 	    });
 	}
 
+	let el = me.getActionEl();
 	if (error) {
 	    me.addCls(['x-form-trigger-wrap-default', 'x-form-trigger-wrap-invalid']);
 	    let errorMsg = gettext("Need at least one mapping");
-	    me.getActionEl().dom.setAttribute('data-errorqtip', errorMsg);
+	    if (el) {
+		el.dom.setAttribute('data-errorqtip', errorMsg);
+	    }
 
 	    return [errorMsg];
 	}
 	me.removeCls(['x-form-trigger-wrap-default', 'x-form-trigger-wrap-invalid']);
-	me.getActionEl().dom.setAttribute('data-errorqtip', "");
+	if (el) {
+	    el.dom.setAttribute('data-errorqtip', "");
+	}
 	return [];
     },
 
-- 
2.20.1





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

* [pbs-devel] [PATCH proxmox-backup v4 2/5] ui: tape/TapeRestore: improve SnapshotGrid
  2021-05-21 10:20 [pbs-devel] [PATCH proxmox-backup v4 0/5] ui: unify and improve tape restore window Dominik Csapak
  2021-05-21 10:20 ` [pbs-devel] [PATCH proxmox-backup v4 1/5] ui: tape/TapeRestore: fix small DataStoreMappingGrid bugs Dominik Csapak
@ 2021-05-21 10:20 ` Dominik Csapak
  2021-05-21 10:20 ` [pbs-devel] [PATCH proxmox-backup v4 3/5] ui: tape/window/TapeRestore: enabling selecting multiple snapshots Dominik Csapak
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Dominik Csapak @ 2021-05-21 10:20 UTC (permalink / raw)
  To: pbs-devel

* handle not rendered call of getErrors
* return 'all' as value if all snaphots where selected
  (for better distinction)
* remove the default height
* add checkChange on stores filterChange
  (now change also fires on the gridfilter plugin change)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/tape/window/TapeRestore.js | 22 ++++++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)

diff --git a/www/tape/window/TapeRestore.js b/www/tape/window/TapeRestore.js
index 2a876619..10624f9a 100644
--- a/www/tape/window/TapeRestore.js
+++ b/www/tape/window/TapeRestore.js
@@ -336,6 +336,13 @@ Ext.define('PBS.TapeManagement.SnapshotGrid', {
 	    }
 	});
 
+	// getSource returns null if data is not filtered
+	let originalData = me.store.getData().getSource() || me.store.getData();
+
+	if (snapshots.length === originalData.length) {
+	    return "all";
+	}
+
 	return snapshots;
     },
 
@@ -347,20 +354,25 @@ Ext.define('PBS.TapeManagement.SnapshotGrid', {
 
     getErrors: function(value) {
 	let me = this;
-	if (me.getSelection() < 1) {
+	if (me.getSelection().length < 1) {
 	    me.addCls(['x-form-trigger-wrap-default', 'x-form-trigger-wrap-invalid']);
 	    let errorMsg = gettext("Need at least one snapshot");
-	    me.getActionEl().dom.setAttribute('data-errorqtip', errorMsg);
+	    let el = me.getActionEl();
+	    if (el) {
+		el.dom.setAttribute('data-errorqtip', errorMsg);
+	    }
 
 	    return [errorMsg];
 	}
 	me.removeCls(['x-form-trigger-wrap-default', 'x-form-trigger-wrap-invalid']);
-	me.getActionEl().dom.setAttribute('data-errorqtip', "");
+	let el = me.getActionEl();
+	if (el) {
+	    el.dom.setAttribute('data-errorqtip', "");
+	}
 	return [];
     },
 
     scrollable: true,
-    height: 350,
     plugins: 'gridfilters',
 
     viewConfig: {
@@ -424,5 +436,7 @@ Ext.define('PBS.TapeManagement.SnapshotGrid', {
 		},
 	    );
 	}
+
+	me.mon(me.store, 'filterchange', () => me.checkChange());
     },
 });
-- 
2.20.1





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

* [pbs-devel] [PATCH proxmox-backup v4 3/5] ui: tape/window/TapeRestore: enabling selecting multiple snapshots
  2021-05-21 10:20 [pbs-devel] [PATCH proxmox-backup v4 0/5] ui: unify and improve tape restore window Dominik Csapak
  2021-05-21 10:20 ` [pbs-devel] [PATCH proxmox-backup v4 1/5] ui: tape/TapeRestore: fix small DataStoreMappingGrid bugs Dominik Csapak
  2021-05-21 10:20 ` [pbs-devel] [PATCH proxmox-backup v4 2/5] ui: tape/TapeRestore: improve SnapshotGrid Dominik Csapak
@ 2021-05-21 10:20 ` Dominik Csapak
  2021-05-21 10:20 ` [pbs-devel] [PATCH proxmox-backup v4 4/5] ui: tape/BackupOverview: also allow to filter by group for restore Dominik Csapak
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Dominik Csapak @ 2021-05-21 10:20 UTC (permalink / raw)
  To: pbs-devel

by including the new snapshotselector. If a whole media-set is to be
restored, select all snapshots

to achieve this, we drop the 'restoreid' and 'datastores' properties
for the restore window, and replace them by a 'prefilter' object
(with 'store' and 'snapshot' properties)

to be able to show the snapshots, we now have to always load the
content of that media-set, so drop the short-circuit if we have
the datastores already.

change the layout of the restore window into a two-step window
so that the first tab is the selection what to restore, and on the
second tab the user chooses where to restore (drive, datastore, etc.)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
the controller has the basic structure of a generic wizard, but not all
features we need yet for other uses. the plan is that when i have time in
the next weeks, i'll use this class as a base for a generic wizard that
i'll put into widget toolkit and rewrite our existing wizards using this

the heights of the grid were found empirically, we sadly have no way
currently to make a field fill the remaining height of an inputpanel
i can look into that in the future

 www/tape/BackupOverview.js     |  27 +-
 www/tape/window/TapeRestore.js | 530 +++++++++++++++++++++++----------
 2 files changed, 381 insertions(+), 176 deletions(-)

diff --git a/www/tape/BackupOverview.js b/www/tape/BackupOverview.js
index 0e105274..eb8ef907 100644
--- a/www/tape/BackupOverview.js
+++ b/www/tape/BackupOverview.js
@@ -19,27 +19,13 @@ Ext.define('PBS.TapeManagement.BackupOverview', {
 	restore: function(view, rI, cI, item, e, rec) {
 	    let me = this;
 
-	    let node = rec;
-	    let mediaset = node.data.is_media_set ? node.data.text : node.data['media-set'];
-	    let uuid = node.data['media-set-uuid'];
-
-	    let list;
-	    let datastores;
-	    if (node.data.restoreid !== undefined) {
-		list = [node.data.restoreid];
-		datastores = [node.data.store];
-	    } else {
-		datastores = node.data.datastores;
-		while (!datastores && node.get('depth') > 2) {
-		    node = node.parentNode;
-		    datastores = node.data.datastores;
-		}
-	    }
+	    let mediaset = rec.data.is_media_set ? rec.data.text : rec.data['media-set'];
+	    let uuid = rec.data['media-set-uuid'];
+	    let prefilter = rec.data.prefilter;
 	    Ext.create('PBS.TapeManagement.TapeRestoreWindow', {
 		mediaset,
 		uuid,
-		datastores,
-		list,
+		prefilter,
 		listeners: {
 		    destroy: function() {
 			me.reload();
@@ -157,7 +143,10 @@ Ext.define('PBS.TapeManagement.BackupOverview', {
 		    entry.leaf = true;
 		    entry.children = [];
 		    entry['media-set'] = media_set;
-		    entry.restoreid = `${entry.store}:${entry.snapshot}`;
+		    entry.prefilter = {
+			store: entry.store,
+			snapshot: entry.snapshot,
+		    };
 		    let iconCls = PBS.Utils.get_type_icon_cls(entry.snapshot);
 		    if (iconCls !== '') {
 			entry.iconCls = `fa ${iconCls}`;
diff --git a/www/tape/window/TapeRestore.js b/www/tape/window/TapeRestore.js
index 10624f9a..6bd35f53 100644
--- a/www/tape/window/TapeRestore.js
+++ b/www/tape/window/TapeRestore.js
@@ -1,11 +1,11 @@
 Ext.define('PBS.TapeManagement.TapeRestoreWindow', {
-    extend: 'Proxmox.window.Edit',
+    extend: 'Ext.window.Window',
     alias: 'widget.pbsTapeRestoreWindow',
     mixins: ['Proxmox.Mixin.CBind'],
 
     width: 800,
+    height: 500,
     title: gettext('Restore Media Set'),
-    submitText: gettext('Restore'),
     url: '/api2/extjs/tape/restore',
     method: 'POST',
     showTaskViewer: true,
@@ -13,188 +13,404 @@ Ext.define('PBS.TapeManagement.TapeRestoreWindow', {
 
     cbindData: function(config) {
 	let me = this;
-	me.isSingle = false;
-	me.listText = "";
-	if (me.list !== undefined) {
-	    me.isSingle = true;
-	    me.listText = me.list.join('<br>');
-	    me.title = gettext('Restore Snapshot');
+	if (me.prefilter !== undefined) {
+	    me.title = gettext('Restore Snapshot(s)');
 	}
 	return {};
     },
 
-    defaults: {
-	labelWidth: 120,
-    },
+    layout: 'fit',
+    bodyPadding: 0,
 
-    referenceHolder: true,
+    controller: {
+	xclass: 'Ext.app.ViewController',
 
-    items: [
-	{
-	    xtype: 'inputpanel',
-
-	    onGetValues: function(values) {
-		let me = this;
-		let datastores = [];
-		if (values.store.toString() !== "") {
-		    datastores.push(values.store);
-		    delete values.store;
-		}
+	panelIsValid: function(panel) {
+	    return panel.query('[isFormField]').every(field => field.isValid());
+	},
 
-		if (values.mapping.toString() !== "") {
-		    datastores.push(values.mapping);
+	checkValidity: function() {
+	    let me = this;
+	    let tabpanel = me.lookup('tabpanel');
+	    let items = tabpanel.items;
+
+	    let checkValidity = true;
+
+	    let indexOfActiveTab = items.indexOf(tabpanel.getActiveTab());
+	    let indexOfLastValidTab = 0;
+
+	    items.each((panel) => {
+		if (checkValidity) {
+		    panel.setDisabled(false);
+		    indexOfLastValidTab = items.indexOf(panel);
+		    if (!me.panelIsValid(panel)) {
+			checkValidity = false;
+		    }
+		} else {
+		    panel.setDisabled(true);
 		}
-		delete values.mapping;
 
-		if (me.up('window').list !== undefined) {
-		    values.snapshots = me.up('window').list;
-		}
+		return true;
+	    });
 
-		values.store = datastores.join(',');
+	    if (indexOfLastValidTab < indexOfActiveTab) {
+		tabpanel.setActiveTab(indexOfLastValidTab);
+	    } else {
+		me.setButtonState(tabpanel.getActiveTab());
+	    }
+	},
 
-		return values;
-	    },
+	setButtonState: function(panel) {
+	    let me = this;
+	    let isValid = me.panelIsValid(panel);
+	    let nextButton = me.lookup('nextButton');
+	    let finishButton = me.lookup('finishButton');
+	    nextButton.setDisabled(!isValid);
+	    finishButton.setDisabled(!isValid);
+	},
 
-	    column1: [
-		{
-		    xtype: 'displayfield',
-		    fieldLabel: gettext('Media Set'),
-		    cbind: {
-			value: '{mediaset}',
-		    },
-		},
-		{
-		    xtype: 'displayfield',
-		    fieldLabel: gettext('Media Set UUID'),
-		    name: 'media-set',
-		    submitValue: true,
-		    cbind: {
-			value: '{uuid}',
-		    },
-		},
-		{
-		    xtype: 'displayfield',
-		    fieldLabel: gettext('Snapshot(s)'),
-		    submitValue: false,
-		    cbind: {
-			hidden: '{!isSingle}',
-			value: '{listText}',
-		    },
-		},
-		{
-		    xtype: 'pbsDriveSelector',
-		    fieldLabel: gettext('Drive'),
-		    name: 'drive',
-		},
-	    ],
+	changeButtonVisibility: function(tabpanel, newItem) {
+	    let me = this;
+	    let items = tabpanel.items;
 
-	    column2: [
-		{
-		    xtype: 'pbsUserSelector',
-		    name: 'notify-user',
-		    fieldLabel: gettext('Notify User'),
-		    emptyText: gettext('Current User'),
-		    value: null,
-		    allowBlank: true,
-		    skipEmptyText: true,
-		    renderer: Ext.String.htmlEncode,
-		},
-		{
-		    xtype: 'pbsUserSelector',
-		    name: 'owner',
-		    fieldLabel: gettext('Owner'),
-		    emptyText: gettext('Current User'),
-		    value: null,
-		    allowBlank: true,
-		    skipEmptyText: true,
-		    renderer: Ext.String.htmlEncode,
-		},
-		{
-		    xtype: 'pbsDataStoreSelector',
-		    fieldLabel: gettext('Target Datastore'),
-		    reference: 'defaultDatastore',
-		    name: 'store',
-		    listeners: {
-			change: function(field, value) {
-			    let me = this;
-			    let grid = me.up('window').lookup('mappingGrid');
-			    grid.setNeedStores(!value);
-			},
-		    },
-		},
-	    ],
+	    let backButton = me.lookup('backButton');
+	    let nextButton = me.lookup('nextButton');
+	    let finishButton = me.lookup('finishButton');
 
-	    columnB: [
-		{
-		    fieldLabel: gettext('Datastore Mapping'),
-		    labelWidth: 200,
-		    hidden: true,
-		    reference: 'mappingLabel',
-		    xtype: 'displayfield',
+	    let isLast = items.last() === newItem;
+	    let isFirst = items.first() === newItem;
+
+	    backButton.setVisible(!isFirst);
+	    nextButton.setVisible(!isLast);
+	    finishButton.setVisible(isLast);
+
+	    me.setButtonState(newItem);
+	},
+
+	previousTab: function() {
+	    let me = this;
+	    let tabpanel = me.lookup('tabpanel');
+	    let index = tabpanel.items.indexOf(tabpanel.getActiveTab());
+	    tabpanel.setActiveTab(index - 1);
+	},
+
+	nextTab: function() {
+	    let me = this;
+	    let tabpanel = me.lookup('tabpanel');
+	    let index = tabpanel.items.indexOf(tabpanel.getActiveTab());
+	    tabpanel.setActiveTab(index + 1);
+	},
+
+	getValues: function() {
+	    let me = this;
+
+	    let values = {};
+
+	    let tabpanel = me.lookup('tabpanel');
+	    tabpanel
+		.query('inputpanel')
+		.forEach((panel) =>
+		    Proxmox.Utils.assemble_field_data(values, panel.getValues()));
+
+	    return values;
+	},
+
+	finish: function() {
+	    let me = this;
+	    let view = me.getView();
+
+	    let values = me.getValues();
+	    let url = view.url;
+	    let method = view.method;
+
+	    Proxmox.Utils.API2Request({
+		url,
+		waitMsgTarget: view,
+		method,
+		params: values,
+		failure: function(response, options) {
+		    Ext.Msg.alert(gettext('Error'), response.htmlStatus);
 		},
-		{
-		    xtype: 'pbsDataStoreMappingField',
-		    reference: 'mappingGrid',
-		    name: 'mapping',
-		    defaultBindProperty: 'value',
-		    hidden: true,
+		success: function(response, options) {
+			// stay around so we can trigger our close events
+			// when background action is completed
+			view.hide();
+
+			Ext.create('Proxmox.window.TaskViewer', {
+			    autoShow: true,
+			    upid: response.result.data,
+			    listeners: {
+				destroy: function() {
+				    view.close();
+				},
+			    },
+			});
 		},
-	    ],
+	    });
 	},
-    ],
 
-    setDataStores: function(datastores) {
-	let me = this;
+	updateDatastores: function() {
+	    let me = this;
+	    let grid = me.lookup('snapshotGrid');
+	    let values = grid.getValue();
+	    if (values === 'all') {
+		values = [];
+	    }
+	    let datastores = {};
+	    values.forEach((snapshot) => {
+		const [datastore] = snapshot.split(':');
+		datastores[datastore] = true;
+	    });
 
-	let label = me.lookup('mappingLabel');
-	let grid = me.lookup('mappingGrid');
-	let defaultField = me.lookup('defaultDatastore');
-
-	if (!datastores || datastores.length <= 1) {
-	    label.setVisible(false);
-	    grid.setVisible(false);
-	    defaultField.setFieldLabel(gettext('Target Datastore'));
-	    defaultField.setAllowBlank(false);
-	    defaultField.setEmptyText("");
-	    return;
-	}
+	    me.setDataStores(Object.keys(datastores));
+	},
 
-	label.setVisible(true);
-	defaultField.setFieldLabel(gettext('Default Datastore'));
-	defaultField.setAllowBlank(true);
-	defaultField.setEmptyText(Proxmox.Utils.NoneText);
+	setDataStores: function(datastores, initial) {
+	    let me = this;
 
-	grid.setDataStores(datastores);
-	grid.setVisible(true);
+	    // save all datastores on the first setting, and
+	    // restore them if we selected all
+	    if (initial) {
+		me.datastores = datastores;
+	    } else if (datastores.length === 0) {
+		datastores = me.datastores;
+	    }
+
+	    let label = me.lookup('mappingLabel');
+	    let grid = me.lookup('mappingGrid');
+	    let defaultField = me.lookup('defaultDatastore');
+
+	    if (!datastores || datastores.length <= 1) {
+		label.setVisible(false);
+		grid.setVisible(false);
+		defaultField.setFieldLabel(gettext('Target Datastore'));
+		defaultField.setAllowBlank(false);
+		defaultField.setEmptyText("");
+		return;
+	    }
+
+	    label.setVisible(true);
+	    defaultField.setFieldLabel(gettext('Default Datastore'));
+	    defaultField.setAllowBlank(true);
+	    defaultField.setEmptyText(Proxmox.Utils.NoneText);
+
+	    grid.setDataStores(datastores);
+	    grid.setVisible(true);
+	},
+
+	updateSnapshots: function() {
+	    let me = this;
+	    let view = me.getView();
+	    let grid = me.lookup('snapshotGrid');
+
+	    Proxmox.Utils.API2Request({
+		waitMsgTarget: view,
+		url: `/tape/media/content?media-set=${view.uuid}`,
+		success: function(response, opt) {
+		    let datastores = {};
+		    for (const content of response.result.data) {
+			datastores[content.store] = true;
+		    }
+		    me.setDataStores(Object.keys(datastores), true);
+		    if (response.result.data.length > 0) {
+			grid.setDisabled(false);
+			grid.setVisible(true);
+			grid.getStore().setData(response.result.data);
+			grid.getSelectionModel().selectAll();
+			// we've shown a big list, center the window again
+			view.center();
+		    }
+		},
+		failure: function() {
+		    // ignore failing api call, maybe catalog is missing
+		    me.setDataStores([], true);
+		},
+	    });
+	},
+
+	control: {
+	    '[isFormField]': {
+		change: 'checkValidity',
+		validitychange: 'checkValidity',
+	    },
+	    'tabpanel': {
+		tabchange: 'changeButtonVisibility',
+	    },
+	},
     },
 
-    initComponent: function() {
-	let me = this;
+    buttons: [
+	{
+	    text: gettext('Back'),
+	    reference: 'backButton',
+	    handler: 'previousTab',
+	    hidden: true,
+	},
+	{
+	    text: gettext('Next'),
+	    reference: 'nextButton',
+	    handler: 'nextTab',
+	},
+	{
+	    text: gettext('Restore'),
+	    reference: 'finishButton',
+	    handler: 'finish',
+	    hidden: true,
+	},
+    ],
 
-	me.callParent();
-	if (me.datastores) {
-	    me.setDataStores(me.datastores);
-	} else {
-	    // use timeout so that the window is rendered already
-	    // for correct masking
-	    setTimeout(function() {
-		Proxmox.Utils.API2Request({
-		    waitMsgTarget: me,
-		    url: `/tape/media/content?media-set=${me.uuid}`,
-		    success: function(response, opt) {
-			let datastores = {};
-			for (const content of response.result.data) {
-			    datastores[content.store] = true;
+    items: [
+	{
+	    xtype: 'tabpanel',
+	    reference: 'tabpanel',
+	    layout: 'fit',
+	    bodyPadding: 10,
+	    items: [
+		{
+		    title: gettext('Snapshot Selection'),
+		    xtype: 'inputpanel',
+		    onGetValues: function(values) {
+			let me = this;
+
+			if (values.snapshots === 'all') {
+			    delete values.snapshots;
+			} else if (Ext.isString(values.snapshots) && values.snapshots) {
+			    values.snapshots = values.snapshots.split(',');
 			}
-			me.setDataStores(Object.keys(datastores));
+
+			return values;
 		    },
-		    failure: function() {
-			// ignore failing api call, maybe catalog is missing
-			me.setDataStores();
+
+		    column1: [
+			{
+			    xtype: 'displayfield',
+			    fieldLabel: gettext('Media Set'),
+			    cbind: {
+				value: '{mediaset}',
+			    },
+			},
+		    ],
+
+		    column2: [
+			{
+			    xtype: 'displayfield',
+			    fieldLabel: gettext('Media Set UUID'),
+			    name: 'media-set',
+			    submitValue: true,
+			    cbind: {
+				value: '{uuid}',
+			    },
+			},
+		    ],
+
+		    columnB: [
+			{
+			    xtype: 'pbsTapeSnapshotGrid',
+			    reference: 'snapshotGrid',
+			    name: 'snapshots',
+			    height: 322,
+			    // will be shown/enabled on successful load
+			    disabled: true,
+			    hidden: true,
+			    listeners: {
+				change: 'updateDatastores',
+			    },
+			    cbind: {
+				prefilter: '{prefilter}',
+			    },
+			},
+		    ],
+		},
+		{
+		    title: gettext('Target'),
+		    xtype: 'inputpanel',
+		    onGetValues: function(values) {
+			let me = this;
+			let datastores = [];
+			if (values.store.toString() !== "") {
+			    datastores.push(values.store);
+			    delete values.store;
+			}
+
+			if (values.mapping.toString() !== "") {
+			    datastores.push(values.mapping);
+			}
+			delete values.mapping;
+
+			values.store = datastores.join(',');
+
+			return values;
 		    },
-		});
-	    }, 10);
-	}
+		    column1: [
+			{
+			    xtype: 'pbsUserSelector',
+			    name: 'notify-user',
+			    fieldLabel: gettext('Notify User'),
+			    emptyText: gettext('Current User'),
+			    value: null,
+			    allowBlank: true,
+			    skipEmptyText: true,
+			    renderer: Ext.String.htmlEncode,
+			},
+			{
+			    xtype: 'pbsUserSelector',
+			    name: 'owner',
+			    fieldLabel: gettext('Owner'),
+			    emptyText: gettext('Current User'),
+			    value: null,
+			    allowBlank: true,
+			    skipEmptyText: true,
+			    renderer: Ext.String.htmlEncode,
+			},
+		    ],
+
+		    column2: [
+			{
+			    xtype: 'pbsDriveSelector',
+			    fieldLabel: gettext('Drive'),
+			    labelWidth: 120,
+			    name: 'drive',
+			},
+			{
+			    xtype: 'pbsDataStoreSelector',
+			    fieldLabel: gettext('Target Datastore'),
+			    labelWidth: 120,
+			    reference: 'defaultDatastore',
+			    name: 'store',
+			    listeners: {
+				change: function(field, value) {
+				    let me = this;
+				    let grid = me.up('window').lookup('mappingGrid');
+				    grid.setNeedStores(!value);
+				},
+			    },
+			},
+		    ],
+
+		    columnB: [
+			{
+			    fieldLabel: gettext('Datastore Mapping'),
+			    labelWidth: 200,
+			    hidden: true,
+			    reference: 'mappingLabel',
+			    xtype: 'displayfield',
+			},
+			{
+			    xtype: 'pbsDataStoreMappingField',
+			    reference: 'mappingGrid',
+			    name: 'mapping',
+			    height: 260,
+			    defaultBindProperty: 'value',
+			    hidden: true,
+			},
+		    ],
+		},
+	    ],
+	},
+    ],
+
+    listeners: {
+	afterrender: 'updateSnapshots',
     },
 });
 
-- 
2.20.1





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

* [pbs-devel] [PATCH proxmox-backup v4 4/5] ui: tape/BackupOverview: also allow to filter by group for restore
  2021-05-21 10:20 [pbs-devel] [PATCH proxmox-backup v4 0/5] ui: unify and improve tape restore window Dominik Csapak
                   ` (2 preceding siblings ...)
  2021-05-21 10:20 ` [pbs-devel] [PATCH proxmox-backup v4 3/5] ui: tape/window/TapeRestore: enabling selecting multiple snapshots Dominik Csapak
@ 2021-05-21 10:20 ` Dominik Csapak
  2021-05-21 10:20 ` [pbs-devel] [PATCH proxmox-backup v4 5/5] ui: tape/BackupOverview: do not reload on restore Dominik Csapak
  2021-05-21 14:34 ` [pbs-devel] applied-series: [PATCH proxmox-backup v4 0/5] ui: unify and improve tape restore window Thomas Lamprecht
  5 siblings, 0 replies; 7+ messages in thread
From: Dominik Csapak @ 2021-05-21 10:20 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/tape/BackupOverview.js | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/www/tape/BackupOverview.js b/www/tape/BackupOverview.js
index eb8ef907..3d49678b 100644
--- a/www/tape/BackupOverview.js
+++ b/www/tape/BackupOverview.js
@@ -182,6 +182,12 @@ Ext.define('PBS.TapeManagement.BackupOverview', {
 			    text,
 			    'media-set-uuid': entry['media-set-uuid'],
 			    leaf: false,
+			    restore: true,
+			    prefilter: {
+				store,
+				snapshot: `${type}/${group}/`,
+			    },
+			    'media-set': media_set,
 			    iconCls: `fa ${iconCls}`,
 			    children: [],
 			});
-- 
2.20.1





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

* [pbs-devel] [PATCH proxmox-backup v4 5/5] ui: tape/BackupOverview: do not reload on restore
  2021-05-21 10:20 [pbs-devel] [PATCH proxmox-backup v4 0/5] ui: unify and improve tape restore window Dominik Csapak
                   ` (3 preceding siblings ...)
  2021-05-21 10:20 ` [pbs-devel] [PATCH proxmox-backup v4 4/5] ui: tape/BackupOverview: also allow to filter by group for restore Dominik Csapak
@ 2021-05-21 10:20 ` Dominik Csapak
  2021-05-21 14:34 ` [pbs-devel] applied-series: [PATCH proxmox-backup v4 0/5] ui: unify and improve tape restore window Thomas Lamprecht
  5 siblings, 0 replies; 7+ messages in thread
From: Dominik Csapak @ 2021-05-21 10:20 UTC (permalink / raw)
  To: pbs-devel

a restore does not change the tape content, so a reload has no benefit here.
since we're touching those lines, change to 'autoShow' property

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/tape/BackupOverview.js | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/www/tape/BackupOverview.js b/www/tape/BackupOverview.js
index 3d49678b..9ab3a128 100644
--- a/www/tape/BackupOverview.js
+++ b/www/tape/BackupOverview.js
@@ -23,15 +23,11 @@ Ext.define('PBS.TapeManagement.BackupOverview', {
 	    let uuid = rec.data['media-set-uuid'];
 	    let prefilter = rec.data.prefilter;
 	    Ext.create('PBS.TapeManagement.TapeRestoreWindow', {
+		autoShow: true,
 		mediaset,
 		uuid,
 		prefilter,
-		listeners: {
-		    destroy: function() {
-			me.reload();
-		    },
-		},
-	    }).show();
+	    });
 	},
 
 	loadContent: async function() {
-- 
2.20.1





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

* [pbs-devel] applied-series: [PATCH proxmox-backup v4 0/5] ui: unify and improve tape restore window
  2021-05-21 10:20 [pbs-devel] [PATCH proxmox-backup v4 0/5] ui: unify and improve tape restore window Dominik Csapak
                   ` (4 preceding siblings ...)
  2021-05-21 10:20 ` [pbs-devel] [PATCH proxmox-backup v4 5/5] ui: tape/BackupOverview: do not reload on restore Dominik Csapak
@ 2021-05-21 14:34 ` Thomas Lamprecht
  5 siblings, 0 replies; 7+ messages in thread
From: Thomas Lamprecht @ 2021-05-21 14:34 UTC (permalink / raw)
  To: Proxmox Backup Server development discussion, Dominik Csapak

On 21.05.21 12:20, Dominik Csapak wrote:
> changes from v3 (sent as v2):
> * use two panel layout for restore window ('what', 'where')
> * rewrite using a controller
> * add some small bugfixes for the snapshotGrid and datastore mapping
> * remove reload after restore
> 
> changes from v2:
> * split patches in proper self-containted commits
> * changed layout in restore window
>    datastore mapping grid is now on the right, other options on the left
> * integrated the selection checkbox into the checkbox selection model of
>   the grid (removing a seperate field)
> * integrated the filter textbox into the gridfilters plugin, to save space
>   (like we do in pves bulk action window)
>   the benefit of this is that we do not have to combine/split the
>   store:snapshots anymore besides when assembling the submitdata,
>   this makes the code a little nicer imho
> 

applied series, thanks!

There's an issue with restoring single groups/snapshots/datastores though, a
early triggered change event tried to de-reference not yet available objects,
resulting in an exception.

Most of that was only for setting different label/emptyText and hiding some
components depending if there was more than one datastore to restore, I could
replaced that logic by using a viewModel relative easily, so I went for that.

I also added a followup to change the datastore-map emptyText when a default
datastore was selected, so that it's clear to the user that not no datastore
but the default datastore is used.

in anyway, the wizard makes this much nicer, especially when restoring
multiple snapshots.

But please split it out in it's own, slightly more general, component definition
in a separate file - should be still easy now and makes it easier to move this to
widget-toolkit and drop this and the PVE one (you naturally can do that now too,
but probably a bit more work..)




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

end of thread, other threads:[~2021-05-21 14:35 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-05-21 10:20 [pbs-devel] [PATCH proxmox-backup v4 0/5] ui: unify and improve tape restore window Dominik Csapak
2021-05-21 10:20 ` [pbs-devel] [PATCH proxmox-backup v4 1/5] ui: tape/TapeRestore: fix small DataStoreMappingGrid bugs Dominik Csapak
2021-05-21 10:20 ` [pbs-devel] [PATCH proxmox-backup v4 2/5] ui: tape/TapeRestore: improve SnapshotGrid Dominik Csapak
2021-05-21 10:20 ` [pbs-devel] [PATCH proxmox-backup v4 3/5] ui: tape/window/TapeRestore: enabling selecting multiple snapshots Dominik Csapak
2021-05-21 10:20 ` [pbs-devel] [PATCH proxmox-backup v4 4/5] ui: tape/BackupOverview: also allow to filter by group for restore Dominik Csapak
2021-05-21 10:20 ` [pbs-devel] [PATCH proxmox-backup v4 5/5] ui: tape/BackupOverview: do not reload on restore Dominik Csapak
2021-05-21 14:34 ` [pbs-devel] applied-series: [PATCH proxmox-backup v4 0/5] ui: unify and improve tape restore window Thomas Lamprecht

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