public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pbs-devel] [PATCH proxmox-backup 1/2] tape: ui: TapeRestore: make datastore mapping selectable
@ 2021-03-25 10:04 Dominik Csapak
  2021-03-25 10:04 ` [pbs-devel] [PATCH proxmox-backup 2/2] api2/types: expand DATASTORE_MAP_LIST_SCHEMA description Dominik Csapak
  0 siblings, 1 reply; 2+ messages in thread
From: Dominik Csapak @ 2021-03-25 10:04 UTC (permalink / raw)
  To: pbs-devel

by adding a custom field (grid) where the user can select
a target datastore for each source datastore on tape

if we have not loaded the content of the media set yet,
we have to load it on window open to get the list of datastores
on the tape

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/tape/BackupOverview.js     |  13 +-
 www/tape/window/TapeRestore.js | 302 ++++++++++++++++++++++++++++-----
 2 files changed, 272 insertions(+), 43 deletions(-)

diff --git a/www/tape/BackupOverview.js b/www/tape/BackupOverview.js
index 0f9a35af..0cc0e18e 100644
--- a/www/tape/BackupOverview.js
+++ b/www/tape/BackupOverview.js
@@ -24,11 +24,18 @@ Ext.define('PBS.TapeManagement.BackupOverview', {
 		return;
 	    }
 
-	    let mediaset = selection[0].data.text;
-	    let uuid = selection[0].data['media-set-uuid'];
+	    let node = selection[0];
+	    let mediaset = node.data.text;
+	    let uuid = node.data['media-set-uuid'];
+	    let datastores = node.data.datastores;
+	    while (!datastores && node.get('depth') > 2) {
+		node = node.parentNode;
+		datastores = node.data.datastores;
+	    }
 	    Ext.create('PBS.TapeManagement.TapeRestoreWindow', {
 		mediaset,
 		uuid,
+		datastores,
 		listeners: {
 		    destroy: function() {
 			me.reload();
@@ -185,6 +192,7 @@ Ext.define('PBS.TapeManagement.BackupOverview', {
 		}
 
 		let storeList = Object.values(stores);
+		let storeNameList = Object.keys(stores);
 		let expand = storeList.length === 1;
 		for (const store of storeList) {
 		    store.children = Object.values(store.tapes);
@@ -198,6 +206,7 @@ Ext.define('PBS.TapeManagement.BackupOverview', {
 		}
 
 		node.set('loaded', true);
+		node.set('datastores', storeNameList);
 		Proxmox.Utils.setErrorMask(view, false);
 		node.expand();
 	    } catch (error) {
diff --git a/www/tape/window/TapeRestore.js b/www/tape/window/TapeRestore.js
index 6b356bda..a9deb745 100644
--- a/www/tape/window/TapeRestore.js
+++ b/www/tape/window/TapeRestore.js
@@ -1,9 +1,9 @@
 Ext.define('PBS.TapeManagement.TapeRestoreWindow', {
     extend: 'Proxmox.window.Edit',
-    alias: 'pbsTapeRestoreWindow',
+    alias: 'widget.pbsTapeRestoreWindow',
     mixins: ['Proxmox.Mixin.CBind'],
 
-    width: 400,
+    width: 800,
     title: gettext('Restore Media Set'),
     url: '/api2/extjs/tape/restore',
     method: 'POST',
@@ -14,52 +14,272 @@ Ext.define('PBS.TapeManagement.TapeRestoreWindow', {
 	labelWidth: 120,
     },
 
+    referenceHolder: true,
+
     items: [
 	{
-	    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: 'inputpanel',
+
+	    onGetValues: function(values) {
+		let me = this;
+		let datastores = [];
+		if (values.store && values.store !== "") {
+		    datastores.push(values.store);
+		    delete values.store;
+		}
+
+		if (values.mapping) {
+		    datastores.push(values.mapping);
+		    delete values.mapping;
+		}
+
+		values.store = datastores.join(',');
+
+		return values;
 	    },
+
+	    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: 'pbsDriveSelector',
+		    fieldLabel: gettext('Drive'),
+		    name: 'drive',
+		},
+	    ],
+
+	    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('Datastore'),
+		    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',
+		    defaultBindProperty: 'value',
+		    hidden: true,
+		},
+	    ],
 	},
+    ],
+
+    setDataStores: function(datastores) {
+	let me = this;
+
+	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('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);
+    },
+
+    initComponent: function() {
+	let me = this;
+
+	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;
+			}
+			me.setDataStores(Object.keys(datastores));
+		    },
+		    failure: function() {
+			// ignore failing api call, maybe catalog is missing
+			me.setDataStores();
+		    },
+		});
+	    }, 10);
+	}
+    },
+});
+
+Ext.define('PBS.TapeManagement.DataStoreMappingGrid', {
+    extend: 'Ext.grid.Panel',
+    alias: 'widget.pbsDataStoreMappingField',
+    mixins: ['Ext.form.field.Field'],
+
+    getValue: function() {
+	let me = this;
+	let datastores = [];
+	me.getStore().each((rec) => {
+	    let source = rec.data.source;
+	    let target = rec.data.target;
+	    if (target && target !== "") {
+		datastores.push(`${source}=${target}`);
+	    }
+	});
+
+	return datastores.join(',');
+    },
+
+    // this determines if we need at least one valid mapping
+    needStores: false,
+
+    setNeedStores: function(needStores) {
+	let me = this;
+	me.needStores = needStores;
+	me.checkChange();
+	me.validate();
+    },
+
+    setValue: function(value) {
+	let me = this;
+	me.setDataStores(value);
+	return me;
+    },
+
+    getErrors: function(value) {
+	let me = this;
+	let error = false;
+
+	if (me.needStores) {
+	    error = true;
+	    me.getStore().each((rec) => {
+		if (rec.data.target) {
+		    error = false;
+		}
+	    });
+	}
+
+	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);
+
+	    return [errorMsg];
+	}
+	me.removeCls(['x-form-trigger-wrap-default', 'x-form-trigger-wrap-invalid']);
+	me.getActionEl().dom.setAttribute('data-errorqtip', "");
+	return [];
+    },
+
+    setDataStores: function(datastores) {
+	let me = this;
+	let store = me.getStore();
+	let data = [];
+
+	for (const datastore of datastores) {
+	    data.push({
+		source: datastore,
+		target: '',
+	    });
+	}
+
+	store.setData(data);
+    },
+
+    viewConfig: {
+	markDirty: false,
+    },
+
+    store: { data: [] },
+
+    columns: [
 	{
-	    xtype: 'pbsDataStoreSelector',
-	    fieldLabel: gettext('Datastore'),
-	    name: 'store',
-	},
-	{
-	    xtype: 'pbsDriveSelector',
-	    fieldLabel: gettext('Drive'),
-	    name: 'drive',
-	},
-	{
-	    xtype: 'pbsUserSelector',
-	    name: 'notify-user',
-	    fieldLabel: gettext('Notify User'),
-	    emptyText: gettext('Current User'),
-	    value: null,
-	    allowBlank: true,
-	    skipEmptyText: true,
-	    renderer: Ext.String.htmlEncode,
+	    text: gettext('Source Datastore'),
+	    dataIndex: 'source',
+	    flex: 1,
 	},
 	{
-	    xtype: 'pbsUserSelector',
-	    name: 'owner',
-	    fieldLabel: gettext('Owner'),
-	    emptyText: gettext('Current User'),
-	    value: null,
-	    allowBlank: true,
-	    skipEmptyText: true,
-	    renderer: Ext.String.htmlEncode,
+	    text: gettext('Target Datastore'),
+	    xtype: 'widgetcolumn',
+	    dataIndex: 'target',
+	    flex: 1,
+	    widget: {
+		xtype: 'pbsDataStoreSelector',
+		allowBlank: true,
+		emptyText: Proxmox.Utils.NoneText,
+		listeners: {
+		    change: function(selector, value) {
+			let me = this;
+			let rec = me.getWidgetRecord();
+			if (!rec) {
+			    return;
+			}
+			rec.set('target', value);
+			me.up('grid').checkChange();
+		    },
+		},
+	    },
 	},
     ],
 });
-- 
2.20.1





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

* [pbs-devel] [PATCH proxmox-backup 2/2] api2/types: expand DATASTORE_MAP_LIST_SCHEMA description
  2021-03-25 10:04 [pbs-devel] [PATCH proxmox-backup 1/2] tape: ui: TapeRestore: make datastore mapping selectable Dominik Csapak
@ 2021-03-25 10:04 ` Dominik Csapak
  0 siblings, 0 replies; 2+ messages in thread
From: Dominik Csapak @ 2021-03-25 10:04 UTC (permalink / raw)
  To: pbs-devel

and give an example

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/api2/types/mod.rs | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/api2/types/mod.rs b/src/api2/types/mod.rs
index fb7ba816..80db98fc 100644
--- a/src/api2/types/mod.rs
+++ b/src/api2/types/mod.rs
@@ -372,7 +372,10 @@ pub const DATASTORE_MAP_ARRAY_SCHEMA: Schema = ArraySchema::new(
     .schema();
 
 pub const DATASTORE_MAP_LIST_SCHEMA: Schema = StringSchema::new(
-    "A list of Datastore mappings (or single datastore), comma separated.")
+    "A list of Datastore mappings (or single datastore), comma separated. \
+    For example 'a=b,e' maps the source datastore 'a' to target 'b and \
+    all other sources to the default 'e'. If no default is given, only the \
+    specified sources are mapped.")
     .format(&ApiStringFormat::PropertyString(&DATASTORE_MAP_ARRAY_SCHEMA))
     .schema();
 
-- 
2.20.1





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

end of thread, other threads:[~2021-03-25 10:05 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-03-25 10:04 [pbs-devel] [PATCH proxmox-backup 1/2] tape: ui: TapeRestore: make datastore mapping selectable Dominik Csapak
2021-03-25 10:04 ` [pbs-devel] [PATCH proxmox-backup 2/2] api2/types: expand DATASTORE_MAP_LIST_SCHEMA description Dominik Csapak

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