From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 674DF1FF13F for ; Thu, 07 May 2026 14:05:04 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id A9FB01702C; Thu, 7 May 2026 14:05:01 +0200 (CEST) Message-ID: <82770e3a-4e89-4b6c-9ff9-b0c9ee097947@proxmox.com> Date: Thu, 7 May 2026 14:04:26 +0200 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Beta Subject: Re: [PATCH manager v2 7/7] ui: lxc/MPEdit: add "idmap" option To: Filip Schauer , pve-devel@lists.proxmox.com References: <20260330141021.151921-1-f.schauer@proxmox.com> <20260330141021.151921-8-f.schauer@proxmox.com> Content-Language: en-US From: Dominik Csapak In-Reply-To: <20260330141021.151921-8-f.schauer@proxmox.com> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1778155358863 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.050 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: CLD34HU7FKGSWCIZ4O665P5NA6EU4HBV X-Message-ID-Hash: CLD34HU7FKGSWCIZ4O665P5NA6EU4HBV X-MailFrom: d.csapak@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 VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: some high level comments: the segmented buttons for the different modes (passthrough, none, custom) but i'd make it grey then (see pwt rrdtype selector on how to do that) but i think we could make the selection simpler even? it's either a 'passthrough' mode (this could be a checkbox) or 'custom' 'none' is just the same as custom but without any mapping defined? so having the grid by default there, and hide/disable it when a 'passthrough' checkbox is enabled would be a bit simpler IMO also i'd like to change the segmented button inside the grid into a dropdown, this is more in line what we have elsewhere (e.g. in the guest import dialog) the segmented button is not ideal in conveying what is selected, especially when there are only two options. also, I'd probably like to see the grid factored out as it's own component, either directly with a field mixin, or with the correct events/callbacks to set the fields. as it is right now, a big part of the lxc mount point edit is just the one field, i think it's large enough to warrant being it's own component. The code itself looks good to me On 3/30/26 4:13 PM, Filip Schauer wrote: > Signed-off-by: Filip Schauer > --- > www/manager6/lxc/MPEdit.js | 212 +++++++++++++++++++++++++++++++++++++ > 1 file changed, 212 insertions(+) > > diff --git a/www/manager6/lxc/MPEdit.js b/www/manager6/lxc/MPEdit.js > index b1f67741..1227ebc0 100644 > --- a/www/manager6/lxc/MPEdit.js > +++ b/www/manager6/lxc/MPEdit.js > @@ -47,6 +47,7 @@ Ext.define('PVE.lxc.MountPointInputPanel', { > setMPOpt('acl', values.acl); > setMPOpt('replicate', values.replicate); > setMPOpt('keepattrs', values.keepattrs); > + setMPOpt('idmap', values.idmap); > > let res = {}; > res[confid] = PVE.Parser.printLxcMountPoint(me.mp); > @@ -131,6 +132,41 @@ Ext.define('PVE.lxc.MountPointInputPanel', { > me.getViewModel().set('type', rec.data.type); > }, > }, > + 'grid proxmoxintegerfield,grid segmentedbutton': { > + change: function (widget, value) { > + let me = this; > + let record = widget.getWidgetRecord(); > + let column = widget.getWidgetColumn(); > + if (!record || !column) { > + return; > + } > + record.set(column.dataIndex, value); > + record.commit(); > + me.updateIdMapField(); > + }, > + }, > + 'field[name=idmap]': { > + change: function (field, value) { > + let me = this; > + let mode = !value ? 'none' : value === 'passthrough' ? value : 'custom'; > + let modeBtn = me.lookup('idmapMode'); > + modeBtn.suspendEvent('change'); > + modeBtn.setValue(mode); > + modeBtn.resumeEvent('change'); > + me.updateIdMapUI(mode, value); > + }, > + }, > + 'segmentedbutton[reference=idmapMode]': { > + change: function (button, mode) { > + let me = this; > + let value = mode === 'passthrough' ? mode : ''; > + let field = me.lookup('idmap'); > + field.suspendEvent('change'); > + field.setValue(value); > + field.resumeEvent('change'); > + me.updateIdMapUI(mode, value); > + }, > + }, > }, > init: function (view) { > let _me = this; > @@ -153,6 +189,50 @@ Ext.define('PVE.lxc.MountPointInputPanel', { > view.setVMConfig(view.vmconfig); > } > }, > + updateIdMapUI: function (mode, value) { > + let me = this; > + let isCustom = mode === 'custom'; > + me.lookup('idmaps').setVisible(isCustom); > + me.lookup('addIdmapButton').setVisible(isCustom); > + > + let store = me.lookup('idmaps').getStore(); > + if (isCustom && value) { > + store.setData( > + value.split(';').map((v) => { > + let [type, ct, host, length] = v.split(':'); > + return { type, ct, host, length }; > + }), > + ); > + } else { > + store.removeAll(); > + } > + }, > + addIdMap: function () { > + let me = this; > + me.lookup('idmaps').getStore().add({ type: 'u', ct: '', host: '', length: '' }); > + me.updateIdMapField(); > + }, > + removeIdMap: function (button) { > + let me = this; > + let record = button.getWidgetRecord(); > + me.lookup('idmaps').getStore().remove(record); > + me.updateIdMapField(); > + }, > + updateIdMapField: function () { > + let me = this; > + > + let value = me > + .lookup('idmaps') > + .getStore() > + .getRange() > + .map(({ data: { type, ct, host, length } }) => `${type}:${ct}:${host}:${length}`) > + .join(';'); > + > + let field = me.lookup('idmap'); > + field.suspendEvent('change'); > + field.setValue(value); > + field.resumeEvent('change'); > + }, > }, > > viewModel: { > @@ -353,6 +433,138 @@ Ext.define('PVE.lxc.MountPointInputPanel', { > }, > }, > ], > + > + advancedColumnB: [ > + { > + xtype: 'fieldcontainer', > + fieldLabel: gettext('ID Mapping'), > + items: [ > + { > + xtype: 'segmentedbutton', > + reference: 'idmapMode', > + defaults: { > + allowDepress: false, > + }, > + items: [ > + { > + text: gettext('None'), > + value: 'none', > + pressed: true, > + }, > + { > + text: gettext('Passthrough'), > + value: 'passthrough', > + tooltip: gettext('UIDs/GIDs in the container match those on disk'), > + }, > + { > + text: gettext('Custom'), > + value: 'custom', > + }, > + ], > + }, > + ], > + }, > + { > + xtype: 'grid', > + height: 170, > + scrollable: true, > + reference: 'idmaps', > + hidden: true, > + viewConfig: { > + emptyText: gettext('No ID maps configured'), > + }, > + store: { > + fields: ['type', 'ct', 'host', 'length'], > + data: [], > + }, > + columns: [ > + { > + text: gettext('ID Type'), > + xtype: 'widgetcolumn', > + dataIndex: 'type', > + widget: { > + xtype: 'segmentedbutton', > + items: [ > + { > + text: 'UID', > + value: 'u', > + }, > + { > + text: 'GID', > + value: 'g', > + }, > + ], > + }, > + flex: 1, > + }, > + { > + text: gettext('Container'), > + xtype: 'widgetcolumn', > + dataIndex: 'ct', > + widget: { > + xtype: 'proxmoxintegerfield', > + margin: '4 0', > + emptyText: gettext('Container'), > + isFormField: false, > + allowBlank: false, > + minValue: 0, > + }, > + flex: 1, > + }, > + { > + text: gettext('Host'), > + xtype: 'widgetcolumn', > + dataIndex: 'host', > + widget: { > + xtype: 'proxmoxintegerfield', > + margin: '4 0', > + emptyText: gettext('Host'), > + isFormField: false, > + allowBlank: false, > + minValue: 0, > + }, > + flex: 1, > + }, > + { > + text: gettext('Range Size'), > + xtype: 'widgetcolumn', > + dataIndex: 'length', > + widget: { > + xtype: 'proxmoxintegerfield', > + margin: '4 0', > + emptyText: gettext('Range Size'), > + isFormField: false, > + allowBlank: false, > + minValue: 1, > + }, > + flex: 1, > + }, > + { > + xtype: 'widgetcolumn', > + width: 40, > + widget: { > + xtype: 'button', > + margin: '4 0', > + iconCls: 'fa fa-trash-o', > + handler: 'removeIdMap', > + }, > + }, > + ], > + }, > + { > + xtype: 'button', > + reference: 'addIdmapButton', > + text: gettext('Add'), > + iconCls: 'fa fa-plus-circle', > + handler: 'addIdMap', > + hidden: true, > + }, > + { > + xtype: 'hidden', > + reference: 'idmap', > + name: 'idmap', > + }, > + ], > }); > > Ext.define('PVE.lxc.MountPointEdit', {