From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 4A3A31FF13B for ; Wed, 11 Mar 2026 16:14:22 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 94DEB303F6; Wed, 11 Mar 2026 16:14:17 +0100 (CET) From: Hannes Laimer To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox-backup v4 7/7] ui: add move namespace action Date: Wed, 11 Mar 2026 16:13:15 +0100 Message-ID: <20260311151315.133637-8-h.laimer@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260311151315.133637-1-h.laimer@proxmox.com> References: <20260311151315.133637-1-h.laimer@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1773241980829 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.068 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: B6JEKVGEH6Z5OWJUVZ6EHYPIPSCCCOEA X-Message-ID-Hash: B6JEKVGEH6Z5OWJUVZ6EHYPIPSCCCOEA X-MailFrom: h.laimer@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: When moving a namespace, exclude the source namespace and its subtree from the 'New Parent' selector. Signed-off-by: Hannes Laimer --- www/Makefile | 1 + www/datastore/Content.js | 1 - www/form/NamespaceSelector.js | 11 +++++ www/window/NamespaceMove.js | 79 +++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 www/window/NamespaceMove.js diff --git a/www/Makefile b/www/Makefile index db775b64..9452879a 100644 --- a/www/Makefile +++ b/www/Makefile @@ -80,6 +80,7 @@ JSSRC= \ window/GroupMove.js \ window/DataStoreEdit.js \ window/NamespaceEdit.js \ + window/NamespaceMove.js \ window/MaintenanceOptions.js \ window/NotesEdit.js \ window/RemoteEdit.js \ diff --git a/www/datastore/Content.js b/www/datastore/Content.js index ccd48616..fb0858e9 100644 --- a/www/datastore/Content.js +++ b/www/datastore/Content.js @@ -678,7 +678,6 @@ Ext.define('PBS.DataStoreContent', { } }, - forgetGroup: function (data) { let me = this; let view = me.getView(); diff --git a/www/form/NamespaceSelector.js b/www/form/NamespaceSelector.js index ddf68254..d349b568 100644 --- a/www/form/NamespaceSelector.js +++ b/www/form/NamespaceSelector.js @@ -90,6 +90,17 @@ Ext.define('PBS.form.NamespaceSelector', { }, }); + if (me.excludeNs) { + me.store.addFilter( + new Ext.util.Filter({ + filterFn: (rec) => { + let ns = rec.data.ns; + return ns !== me.excludeNs && !ns.startsWith(`${me.excludeNs}/`); + }, + }), + ); + } + me.callParent(); }, }); diff --git a/www/window/NamespaceMove.js b/www/window/NamespaceMove.js new file mode 100644 index 00000000..415d509d --- /dev/null +++ b/www/window/NamespaceMove.js @@ -0,0 +1,79 @@ +Ext.define('PBS.window.NamespaceMove', { + extend: 'Proxmox.window.Edit', + alias: 'widget.pbsNamespaceMove', + mixins: ['Proxmox.Mixin.CBind'], + + onlineHelp: 'storage-namespaces', + + submitText: gettext('Move'), + isCreate: true, + showTaskViewer: true, + + cbind: { + url: '/api2/extjs/admin/datastore/{datastore}/namespace', + title: (get) => Ext.String.format(gettext("Move Namespace '{0}'"), get('namespace')), + }, + method: 'PUT', + + width: 450, + fieldDefaults: { + labelWidth: 120, + }, + + cbindData: function (initialConfig) { + let ns = initialConfig.namespace ?? ''; + let parts = ns.split('/'); + return { nsName: parts[parts.length - 1] }; + }, + + // Returns the new-ns path that was submitted, for use by the caller after success. + getNewNamespace: function () { + let me = this; + let parent = me.down('[name=parent]').getValue() || ''; + let name = me.down('[name=name]').getValue(); + return parent ? `${parent}/${name}` : name; + }, + + items: { + xtype: 'inputpanel', + onGetValues: function (values) { + let parent = values.parent || ''; + let newNs = parent ? `${parent}/${values.name}` : values.name; + return { + ns: this.up('window').namespace, + 'new-ns': newNs, + }; + }, + items: [ + { + xtype: 'displayfield', + fieldLabel: gettext('Namespace'), + cbind: { + value: '{namespace}', + }, + }, + { + xtype: 'pbsNamespaceSelector', + name: 'parent', + fieldLabel: gettext('New Parent'), + allowBlank: true, + cbind: { + datastore: '{datastore}', + excludeNs: '{namespace}', + }, + }, + { + xtype: 'proxmoxtextfield', + name: 'name', + fieldLabel: gettext('New Name'), + allowBlank: false, + maxLength: 31, + regex: PBS.Utils.SAFE_ID_RE, + regexText: gettext("Only alpha numerical, '_' and '-' (if not at start) allowed"), + cbind: { + value: '{nsName}', + }, + }, + ], + }, +}); -- 2.47.3