mirror of
https://git.proxmox.com/git/proxmox-backup
synced 2025-06-02 02:59:59 +00:00

Fix switching the source for group filters based on the sync jobs
sync direction.
The helper to set the local namespace for the group filers was
introduced in commit 43a92c8c
("ui: group filter: allow to set
namespace for local datastore"), but never used because lost during
subsequent iterations of reworking the patch series.
The switching is corrected by:
- correctly initializing the local store and namespace for the group
filer of sync jobs in push direction in the controller init, if a
datastore is set.
- fixing an incorrect check for the sync direction in the remote
datastore selector change listener.
- conditionally switching namespace to be set for the group filter in
the remote and local namespace selector change listeners.
- conditionally switching datastore to be set for the group filter in
the local datastore selector change listener.
Reported-by: Lukas Wagner <l.wagner@proxmox.com>
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
Tested-by: Dominik Csapak <d.csapak@proxmox.com>
437 lines
11 KiB
JavaScript
437 lines
11 KiB
JavaScript
Ext.define('PBS.window.SyncJobEdit', {
|
|
extend: 'Proxmox.window.Edit',
|
|
alias: 'widget.pbsSyncJobEdit',
|
|
mixins: ['Proxmox.Mixin.CBind'],
|
|
|
|
userid: undefined,
|
|
|
|
onlineHelp: 'syncjobs',
|
|
|
|
isAdd: true,
|
|
|
|
subject: gettext('Sync Job - Pull Direction'),
|
|
|
|
bodyPadding: 0,
|
|
|
|
fieldDefaults: { labelWidth: 120 },
|
|
defaultFocus: 'proxmoxtextfield[name=comment]',
|
|
|
|
cbindData: function(initialConfig) {
|
|
let me = this;
|
|
|
|
let baseurl = '/api2/extjs/config/sync';
|
|
let id = initialConfig.id;
|
|
|
|
me.isCreate = !id;
|
|
me.url = id ? `${baseurl}/${id}` : baseurl;
|
|
me.method = id ? 'PUT' : 'POST';
|
|
me.autoLoad = !!id;
|
|
me.scheduleValue = id ? null : 'hourly';
|
|
me.authid = id ? null : Proxmox.UserName;
|
|
me.editDatastore = me.datastore === undefined && me.isCreate;
|
|
|
|
if (me.syncDirection === 'push') {
|
|
me.subject = gettext('Sync Job - Push Direction');
|
|
me.syncDirectionPush = true;
|
|
me.syncRemoteLabel = gettext('Target Remote');
|
|
me.syncRemoteDatastore = gettext('Target Datastore');
|
|
me.syncRemoteNamespace = gettext('Target Namespace');
|
|
me.syncLocalOwner = gettext('Local User');
|
|
// Sync direction request parameter is only required for creating new jobs,
|
|
// for edit and delete it is derived from the job config given by it's id.
|
|
if (me.isCreate) {
|
|
me.extraRequestParams = {
|
|
"sync-direction": 'push',
|
|
};
|
|
}
|
|
} else {
|
|
me.subject = gettext('Sync Job - Pull Direction');
|
|
me.syncDirectionPush = false;
|
|
me.syncRemoteLabel = gettext('Source Remote');
|
|
me.syncRemoteDatastore = gettext('Source Datastore');
|
|
me.syncRemoteNamespace = gettext('Source Namespace');
|
|
me.syncLocalOwner = gettext('Local Owner');
|
|
}
|
|
|
|
return { };
|
|
},
|
|
|
|
controller: {
|
|
xclass: 'Ext.app.ViewController',
|
|
control: {
|
|
'pbsDataStoreSelector[name=store]': {
|
|
change: 'storeChange',
|
|
},
|
|
},
|
|
|
|
storeChange: function(field, value) {
|
|
let view = this.getView();
|
|
let nsSelector = view.down('pbsNamespaceSelector[name=ns]');
|
|
nsSelector.setDatastore(value);
|
|
},
|
|
|
|
init: function() {
|
|
let view = this.getView();
|
|
if (view.syncDirectionPush && view.datastore !== undefined) {
|
|
let localNs = view.down('pbsNamespaceSelector[name=ns]').getValue();
|
|
view.down('pbsGroupFilter').setLocalNamespace(view.datastore, localNs);
|
|
}
|
|
},
|
|
},
|
|
|
|
setValues: function(values) {
|
|
let me = this;
|
|
if (values.id && !values.remote) {
|
|
values.location = 'local';
|
|
} else {
|
|
values.location = 'remote';
|
|
}
|
|
me.callParent([values]);
|
|
},
|
|
|
|
items: {
|
|
xtype: 'tabpanel',
|
|
bodyPadding: 10,
|
|
border: 0,
|
|
items: [
|
|
{
|
|
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');
|
|
PBS.Utils.delete_if_default(values, 'remote');
|
|
if (typeof values.delete === 'string') {
|
|
values.delete = values.delete.split(',');
|
|
}
|
|
}
|
|
return values;
|
|
},
|
|
cbind: {
|
|
isCreate: '{isCreate}', // pass it through
|
|
},
|
|
column1: [
|
|
{
|
|
xtype: 'pmxDisplayEditField',
|
|
fieldLabel: gettext('Local Datastore'),
|
|
name: 'store',
|
|
submitValue: true,
|
|
cbind: {
|
|
editable: '{editDatastore}',
|
|
value: '{datastore}',
|
|
},
|
|
editConfig: {
|
|
xtype: 'pbsDataStoreSelector',
|
|
allowBlank: false,
|
|
},
|
|
listeners: {
|
|
change: function(field, localStore) {
|
|
let me = this;
|
|
let view = me.up('pbsSyncJobEdit');
|
|
if (view.syncDirectionPush) {
|
|
let localNs = view.down('pbsNamespaceSelector[name=ns]').getValue();
|
|
view.down('pbsGroupFilter').setLocalNamespace(localStore, localNs);
|
|
}
|
|
},
|
|
},
|
|
},
|
|
{
|
|
xtype: 'pbsNamespaceSelector',
|
|
fieldLabel: gettext('Local Namespace'),
|
|
name: 'ns',
|
|
cbind: {
|
|
datastore: '{datastore}',
|
|
},
|
|
listeners: {
|
|
change: function(field, localNs) {
|
|
let me = this;
|
|
let view = me.up('pbsSyncJobEdit');
|
|
|
|
if (view.syncDirectionPush) {
|
|
let localStore = view.down('field[name=store]').getValue();
|
|
view.down('pbsGroupFilter').setLocalNamespace(localStore, localNs);
|
|
}
|
|
|
|
let remoteNs = view.down('pbsRemoteNamespaceSelector[name=remote-ns]').getValue();
|
|
let maxDepthField = view.down('field[name=max-depth]');
|
|
maxDepthField.setLimit(localNs, remoteNs);
|
|
maxDepthField.validate();
|
|
},
|
|
},
|
|
},
|
|
{
|
|
xtype: 'pbsAuthidSelector',
|
|
name: 'owner',
|
|
cbind: {
|
|
fieldLabel: '{syncLocalOwner}',
|
|
value: '{authid}',
|
|
deleteEmpty: '{!isCreate}',
|
|
},
|
|
},
|
|
{
|
|
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
|
|
},
|
|
],
|
|
|
|
column2: [
|
|
{
|
|
xtype: 'radiogroup',
|
|
fieldLabel: gettext('Location'),
|
|
defaultType: 'radiofield',
|
|
cbind: {
|
|
disabled: '{syncDirectionPush}',
|
|
},
|
|
items: [
|
|
{
|
|
boxLabel: 'Local',
|
|
name: 'location',
|
|
inputValue: 'local',
|
|
submitValue: false,
|
|
},
|
|
{
|
|
boxLabel: 'Remote',
|
|
name: 'location',
|
|
inputValue: 'remote',
|
|
submitValue: false,
|
|
checked: true,
|
|
},
|
|
],
|
|
listeners: {
|
|
change: function(_group, radio) {
|
|
let me = this;
|
|
let form = me.up('pbsSyncJobEdit');
|
|
let nsField = form.down('field[name=remote-ns]');
|
|
let rateLimitField = form.down('field[name=rate-in]');
|
|
let remoteField = form.down('field[name=remote]');
|
|
let storeField = form.down('field[name=remote-store]');
|
|
|
|
if (!storeField.value) {
|
|
nsField.clearValue();
|
|
nsField.setDisabled(true);
|
|
}
|
|
|
|
let isLocalSync = radio.location === 'local';
|
|
rateLimitField.setDisabled(isLocalSync);
|
|
remoteField.allowBlank = isLocalSync;
|
|
remoteField.setDisabled(isLocalSync);
|
|
storeField.setDisabled(!isLocalSync && !remoteField.value);
|
|
if (isLocalSync === !!remoteField.value) {
|
|
remoteField.clearValue();
|
|
}
|
|
|
|
if (isLocalSync) {
|
|
storeField.setDisabled(false);
|
|
rateLimitField.setValue(null);
|
|
storeField.setRemote(null, true);
|
|
} else {
|
|
storeField.clearValue();
|
|
remoteField.validate();
|
|
}
|
|
},
|
|
},
|
|
},
|
|
{
|
|
cbind: {
|
|
fieldLabel: '{syncRemoteLabel}',
|
|
},
|
|
xtype: 'pbsRemoteSelector',
|
|
allowBlank: false,
|
|
name: 'remote',
|
|
skipEmptyText: true,
|
|
listeners: {
|
|
change: function(f, value) {
|
|
let me = this;
|
|
let remoteStoreField = me.up('pbsSyncJobEdit').down('field[name=remote-store]');
|
|
remoteStoreField.setRemote(value);
|
|
let rateLimitField = me.up('pbsSyncJobEdit').down('field[name=rate-in]');
|
|
rateLimitField.setDisabled(!value);
|
|
if (!value) {
|
|
rateLimitField.setValue(null);
|
|
}
|
|
let remoteNamespaceField = me.up('pbsSyncJobEdit').down('field[name=remote-ns]');
|
|
remoteNamespaceField.setRemote(value);
|
|
},
|
|
},
|
|
},
|
|
{
|
|
xtype: 'pbsRemoteStoreSelector',
|
|
allowBlank: false,
|
|
autoSelect: false,
|
|
name: 'remote-store',
|
|
cbind: {
|
|
datastore: '{datastore}',
|
|
fieldLabel: '{syncRemoteDatastore}',
|
|
},
|
|
listeners: {
|
|
change: function(field, value) {
|
|
let me = this;
|
|
let remoteField = me.up('pbsSyncJobEdit').down('field[name=remote]');
|
|
let remote = remoteField.getValue();
|
|
let remoteNamespaceField = me.up('pbsSyncJobEdit').down('field[name=remote-ns]');
|
|
remoteNamespaceField.setRemote(remote);
|
|
remoteNamespaceField.setRemoteStore(value);
|
|
|
|
let view = me.up('pbsSyncJobEdit');
|
|
if (!view.syncDirectionPush) {
|
|
me.up('tabpanel').down('pbsGroupFilter').setRemoteDatastore(remote, value);
|
|
} else {
|
|
let localStore = me.up('pbsSyncJobEdit').down('field[name=store]').getValue();
|
|
me.up('tabpanel').down('pbsGroupFilter').setLocalDatastore(localStore);
|
|
}
|
|
},
|
|
},
|
|
},
|
|
{
|
|
cbind: {
|
|
fieldLabel: '{syncRemoteNamespace}',
|
|
},
|
|
xtype: 'pbsRemoteNamespaceSelector',
|
|
allowBlank: true,
|
|
autoSelect: false,
|
|
name: 'remote-ns',
|
|
disabled: true,
|
|
listeners: {
|
|
change: function(field, remoteNs) {
|
|
let me = this;
|
|
let view = me.up('pbsSyncJobEdit');
|
|
|
|
let remote = view.down('field[name=remote]').getValue();
|
|
let remoteStore = view.down('field[name=remote-store]').getValue();
|
|
|
|
if (!view.syncDirectionPush) {
|
|
me.up('tabpanel').down('pbsGroupFilter').setRemoteNamespace(remote, remoteStore, remoteNs);
|
|
}
|
|
|
|
let localNs = view.down('pbsNamespaceSelector[name=ns]').getValue();
|
|
let maxDepthField = view.down('field[name=max-depth]');
|
|
maxDepthField.setLimit(localNs, remoteNs);
|
|
maxDepthField.validate();
|
|
},
|
|
},
|
|
},
|
|
{
|
|
xtype: 'pbsNamespaceMaxDepthReduced',
|
|
name: 'max-depth',
|
|
fieldLabel: gettext('Max. Depth'),
|
|
cbind: {
|
|
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,
|
|
},
|
|
],
|
|
|
|
columnB: [
|
|
{
|
|
fieldLabel: gettext('Comment'),
|
|
xtype: 'proxmoxtextfield',
|
|
name: 'comment',
|
|
cbind: {
|
|
deleteEmpty: '{!isCreate}',
|
|
},
|
|
},
|
|
],
|
|
advancedColumn1: [
|
|
{
|
|
xtype: 'pmxDisplayEditField',
|
|
fieldLabel: gettext('Job ID'),
|
|
emptyText: gettext('Autogenerate'),
|
|
name: 'id',
|
|
allowBlank: true,
|
|
regex: PBS.Utils.SAFE_ID_RE,
|
|
cbind: {
|
|
editable: '{isCreate}',
|
|
},
|
|
},
|
|
{
|
|
fieldLabel: gettext('Transfer Last'),
|
|
xtype: 'pbsPruneKeepInput',
|
|
name: 'transfer-last',
|
|
emptyText: gettext('all'),
|
|
autoEl: {
|
|
tag: 'div',
|
|
'data-qtip': gettext('The maximum amount of snapshots to be transferred (per group)'),
|
|
},
|
|
cbind: {
|
|
deleteEmpty: '{!isCreate}',
|
|
},
|
|
},
|
|
{
|
|
fieldLabel: gettext('Re-sync corrupt snapshots'),
|
|
xtype: 'proxmoxcheckbox',
|
|
name: 'resync-corrupt',
|
|
autoEl: {
|
|
tag: 'div',
|
|
'data-qtip': gettext('Re-sync snapshots, whose verification failed.'),
|
|
},
|
|
cbind: {
|
|
disabled: '{syncDirectionPush}',
|
|
},
|
|
uncheckedValue: false,
|
|
value: false,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
xtype: 'inputpanel',
|
|
onGetValues: function(values) {
|
|
let me = this;
|
|
PBS.Utils.delete_if_default(values, 'group-filter');
|
|
if (Ext.isArray(values['group-filter'])) {
|
|
if (values['group-filter'].length === 0) {
|
|
delete values['group-filter'];
|
|
values.delete = 'group-filter';
|
|
} else {
|
|
// merge duplicates
|
|
values['group-filter'] = [...new Set(values['group-filter'])];
|
|
}
|
|
}
|
|
if (me.isCreate) {
|
|
delete values.delete;
|
|
}
|
|
return values;
|
|
},
|
|
cbind: {
|
|
isCreate: '{isCreate}', // pass it through
|
|
},
|
|
title: gettext('Group Filter'),
|
|
items: [
|
|
{
|
|
xtype: 'pbsGroupFilter',
|
|
name: 'group-filter',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
});
|