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 D99401FF137 for ; Tue, 31 Mar 2026 14:27:38 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 559591BADC; Tue, 31 Mar 2026 14:28:06 +0200 (CEST) Message-ID: <5280aca4-a6a6-4fac-88fd-d5d0334df57e@proxmox.com> Date: Tue, 31 Mar 2026 14:27:26 +0200 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH proxmox-backup v5 8/9] ui: add move namespace action To: Dominik Csapak , pbs-devel@lists.proxmox.com References: <20260319161325.206846-1-h.laimer@proxmox.com> <20260319161325.206846-9-h.laimer@proxmox.com> <2290f062-22b9-48ae-b43c-9e7bca683497@proxmox.com> Content-Language: en-US From: Hannes Laimer In-Reply-To: <2290f062-22b9-48ae-b43c-9e7bca683497@proxmox.com> Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1774959991084 X-SPAM-LEVEL: Spam detection results: 0 AWL -1.418 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 RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 1 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 1 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 1 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. 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: EFATH3NWLMRUS4GIPG6NY5UEFBDE2MEK X-Message-ID-Hash: EFATH3NWLMRUS4GIPG6NY5UEFBDE2MEK 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: On 2026-03-24 11:56, Dominik Csapak wrote: > looks mostly fine > > preexisting but slightly confusing: > > the action of a namespace can only be done from that namespace itself, > not from one level above. maybe it would be sensible to make > that possible as well? yes! also thought that, but I'd rather do that separately from this > > otherwise one comment inline > > On 3/19/26 5:13 PM, Hannes Laimer wrote: >> 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      | 27 +++++++++++- >>   www/form/NamespaceSelector.js | 11 +++++ >>   www/window/NamespaceMove.js   | 79 +++++++++++++++++++++++++++++++++++ >>   4 files changed, 117 insertions(+), 1 deletion(-) >>   create mode 100644 www/window/NamespaceMove.js >> >> diff --git a/www/Makefile b/www/Makefile >> index 7745d9f4..f2011af3 100644 >> --- a/www/Makefile >> +++ b/www/Makefile >> @@ -80,6 +80,7 @@ JSSRC=                            \ >>       window/CreateDirectory.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 b56cc4ac..e3e0d0c9 100644 >> --- a/www/datastore/Content.js >> +++ b/www/datastore/Content.js >> @@ -667,6 +667,26 @@ Ext.define('PBS.DataStoreContent', { >>               }); >>           }, >>   +        moveNS: function () { >> +            let me = this; >> +            let view = me.getView(); >> +            if (!view.namespace || view.namespace === '') { >> +                return; >> +            } >> +            let win = Ext.create('PBS.window.NamespaceMove', { >> +                datastore: view.datastore, >> +                namespace: view.namespace, >> +                taskDone: (success) => { >> +                    if (success) { >> +                        let newNs = win.getNewNamespace(); >> +                        view.down('pbsNamespaceSelector').store?.load(); >> +                        me.nsChange(null, newNs); >> +                    } >> +                }, >> +            }); >> +            win.show(); >> +        }, >> + >>           moveGroup: function (data) { >>               let me = this; >>               let view = me.getView(); >> @@ -685,6 +705,8 @@ Ext.define('PBS.DataStoreContent', { >>               let me = this; >>               if (data.ty === 'group') { >>                   me.moveGroup(data); >> +            } else if (data.ty === 'ns') { >> +                me.moveNS(); >>               } >>           }, >>   @@ -1113,10 +1135,13 @@ Ext.define('PBS.DataStoreContent', { >>                           if (data.ty === 'group') { >>                               return Ext.String.format(gettext("Move >> group '{0}'"), v); >>                           } >> -                        return ''; >> +                        return Ext.String.format(gettext("Move >> namespace '{0}'"), v); > > this tooltip is shown for all entries when hovering, even when he icon > is not visible, probably due to it never being disabled > > i think verify and remove have the same problem... > >>                       }, >>                       getClass: (v, m, { data }) => { >>                           if (data.ty === 'group') { return 'fa fa- >> arrows'; } >> +                        if (data.ty === 'ns' && !data.isRootNS && >> data.ns === undefined) { >> +                            return 'fa fa-arrows'; >> +                        } >>                           return 'pmx-hidden'; >>                       }, >>                       isActionDisabled: (v, r, c, i, { data }) => false, >> 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}', >> +                }, >> +            }, >> +        ], >> +    }, >> +}); >