diff --git a/www/config/SyncView.js b/www/config/SyncView.js index 92ba15d3..a90e9a70 100644 --- a/www/config/SyncView.js +++ b/www/config/SyncView.js @@ -1,8 +1,9 @@ Ext.define('pbs-sync-jobs-status', { extend: 'Ext.data.Model', fields: [ - 'id', 'owner', 'remote', 'remote-store', 'store', 'schedule', 'group-filter', - 'next-run', 'last-run-upid', 'last-run-state', 'last-run-endtime', + 'id', 'owner', 'remote', 'remote-store', 'remote-ns', 'store', 'ns', + 'schedule', 'group-filter', 'next-run', 'last-run-upid', 'last-run-state', + 'last-run-endtime', { name: 'duration', calculate: function(data) { @@ -195,6 +196,13 @@ Ext.define('PBS.config.SyncJobView', { width: 120, sortable: true, }, + { + header: gettext('Namespace'), + dataIndex: 'ns', + width: 120, + sortable: true, + renderer: PBS.Utils.render_optional_namespace, + }, { header: gettext('Remote ID'), dataIndex: 'remote', @@ -207,6 +215,19 @@ Ext.define('PBS.config.SyncJobView', { width: 120, sortable: true, }, + { + header: gettext('Remote Namespace'), + dataIndex: 'remote-ns', + width: 120, + sortable: true, + renderer: PBS.Utils.render_optional_namespace, + }, + { + header: gettext('Max. Recursion'), + dataIndex: 'max-depth', + width: 10, + sortable: true, + }, { header: gettext('Owner'), dataIndex: 'owner', diff --git a/www/form/GroupFilter.js b/www/form/GroupFilter.js index 2f6a2bac..11d7d48e 100644 --- a/www/form/GroupFilter.js +++ b/www/form/GroupFilter.js @@ -210,8 +210,13 @@ Ext.define('PBS.form.GroupFilter', { let url; if (me.remote) { url = `/api2/json/config/remote/${me.remote}/scan/${me.datastore}/groups`; + if (me.namespace) { + url += `?namespace=${me.namespace}`; + } } else if (me.datastore) { url = `/api2/json/admin/datastore/${me.datastore}/groups`; + } else { + return; } me.setDsStoreUrl(url); me.dsStore.load({ @@ -237,11 +242,23 @@ Ext.define('PBS.form.GroupFilter', { setRemoteDatastore: function(remote, datastore) { let me = this; - if (me.remote === remote && me.datastore === datastore) { + if (me.remote === remote && me.datastore === datastore && me.namespace === undefined) { return; } me.remote = remote; me.datastore = datastore; + me.namespace = undefined; + me.updateGroupSelectors(); + }, + + setRemoteNamespace: function(remote, datastore, namespace) { + let me = this; + if (me.remote === remote && me.datastore === datastore && me.namespace === namespace) { + return; + } + me.remote = remote; + me.datastore = datastore; + me.namespace = namespace; me.updateGroupSelectors(); }, diff --git a/www/window/SyncJobEdit.js b/www/window/SyncJobEdit.js index 1cb2eca1..571398e3 100644 --- a/www/window/SyncJobEdit.js +++ b/www/window/SyncJobEdit.js @@ -84,6 +84,109 @@ Ext.define('PBS.form.RemoteStoreSelector', { }, }); +Ext.define('PBS.form.RemoteNamespaceSelector', { + extend: 'Proxmox.form.ComboGrid', + alias: 'widget.pbsRemoteNamespaceSelector', + + queryMode: 'local', + + valueField: 'ns', + displayField: 'ns', + emptyText: PBS.Utils.render_optional_namespace(''), + notFoundIsValid: true, + + matchFieldWidth: false, + listConfig: { + loadingText: gettext('Scanning...'), + width: 350, + columns: [ + { + header: gettext('Namespace'), + sortable: true, + dataIndex: 'ns', + renderer: PBS.Utils.render_optional_namespace, // FIXME proper root-aware renderer + flex: 1, + }, + { + header: gettext('Comment'), + dataIndex: 'comment', + renderer: Ext.String.htmlEncode, + flex: 1, + }, + ], + }, + + doRawQuery: function() { + // do nothing. + }, + + setRemote: function(remote) { + let me = this; + + if (me.remote === remote) { + return; + } + + me.remote = remote; + + let store = me.store; + store.removeAll(); + + me.setDisabled(true); + me.clearValue(); + }, + + setRemoteStore: function(remoteStore) { + let me = this; + + if (me.remoteStore === remoteStore) { + return; + } + + me.remoteStore = remoteStore; + + let store = me.store; + store.removeAll(); + + if (me.remote && me.remoteStore) { + me.setDisabled(false); + if (!me.firstLoad) { + me.clearValue(); + } + + store.proxy.url = '/api2/json/config/remote/' + encodeURIComponent(me.remote) + '/scan/' + encodeURIComponent(me.remoteStore) + '/namespaces'; + store.load(); + + me.firstLoad = false; + } else { + me.setDisabled(true); + me.clearValue(); + } + }, + + initComponent: function() { + let me = this; + + me.firstLoad = true; + + let store = Ext.create('Ext.data.Store', { + fields: ['ns', 'comment'], + proxy: { + type: 'proxmox', + url: '/api2/json/config/remote/' + encodeURIComponent(me.remote) + '/scan', + }, + }); + + store.sort('store', 'ASC'); + + Ext.apply(me, { + store: store, + }); + + me.callParent(); + }, +}); + Ext.define('PBS.window.SyncJobEdit', { extend: 'Proxmox.window.Edit', @@ -156,6 +259,20 @@ Ext.define('PBS.window.SyncJobEdit', { allowBlank: false, }, }, + // TODO: make this hot-reloadable based on local store selection? + { + xtype: 'pmxDisplayEditField', + fieldLabel: gettext('Local Namespace'), + name: 'ns', + cbind: { + datastore: '{datastore}', + }, + editable: true, + submitValue: true, + //editConfig: { + // xtype: 'pbsNamespaceSelector', + //}, + }, { fieldLabel: gettext('Local Owner'), xtype: 'pbsAuthidSelector', @@ -166,15 +283,22 @@ Ext.define('PBS.window.SyncJobEdit', { }, }, { - 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?'), + fieldLabel: gettext('Sync Schedule'), + xtype: 'pbsCalendarEvent', + name: 'schedule', + emptyText: gettext('none (disabled)'), + cbind: { + deleteEmpty: '{!isCreate}', + value: '{scheduleValue}', }, - uncheckedValue: false, - value: false, + }, + { + 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 }, ], @@ -189,6 +313,8 @@ Ext.define('PBS.window.SyncJobEdit', { let me = this; let remoteStoreField = me.up('pbsSyncJobEdit').down('field[name=remote-store]'); remoteStoreField.setRemote(value); + let remoteNamespaceField = me.up('pbsSyncJobEdit').down('field[name=remote-ns]'); + remoteNamespaceField.setRemote(value); }, }, }, @@ -204,27 +330,47 @@ Ext.define('PBS.window.SyncJobEdit', { 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); 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}', + fieldLabel: gettext('Source Namespace'), + xtype: 'pbsRemoteNamespaceSelector', + allowBlank: true, + autoSelect: false, + name: 'remote-ns', + disabled: true, + listeners: { + change: function(field, value) { + let me = this; + let remoteField = me.up('pbsSyncJobEdit').down('field[name=remote]'); + let remote = remoteField.getValue(); + let remoteStoreField = me.up('pbsSyncJobEdit').down('field[name=remote-store]'); + let remoteStore = remoteStoreField.getValue(); + me.up('tabpanel').down('pbsGroupFilter').setRemoteNamespace(remote, remoteStore, value); + }, }, }, { - 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 + xtype: 'pbsNamespaceMaxDepth', + name: 'max-depth', + fieldLabel: gettext('Max. Depth'), + deleteEmpty: true, + }, + { + 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, }, ],