tape: ui: TapeRestore: make datastore mapping selectable
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>
This commit is contained in:
parent
a32bb86df9
commit
657c47db35
@ -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) {
|
||||
|
@ -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: '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: [
|
||||
{
|
||||
text: gettext('Source Datastore'),
|
||||
dataIndex: 'source',
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
xtype: 'displayfield',
|
||||
fieldLabel: gettext('Media Set UUID'),
|
||||
name: 'media-set',
|
||||
submitValue: true,
|
||||
cbind: {
|
||||
value: '{uuid}',
|
||||
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();
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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,
|
||||
},
|
||||
{
|
||||
xtype: 'pbsUserSelector',
|
||||
name: 'owner',
|
||||
fieldLabel: gettext('Owner'),
|
||||
emptyText: gettext('Current User'),
|
||||
value: null,
|
||||
allowBlank: true,
|
||||
skipEmptyText: true,
|
||||
renderer: Ext.String.htmlEncode,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user