public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Dominik Csapak <d.csapak@proxmox.com>
To: Filip Schauer <f.schauer@proxmox.com>, pve-devel@lists.proxmox.com
Subject: Re: [PATCH manager v2 7/7] ui: lxc/MPEdit: add "idmap" option
Date: Thu, 7 May 2026 14:04:26 +0200	[thread overview]
Message-ID: <82770e3a-4e89-4b6c-9ff9-b0c9ee097947@proxmox.com> (raw)
In-Reply-To: <20260330141021.151921-8-f.schauer@proxmox.com>

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 <f.schauer@proxmox.com>
> ---
>   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', {





  reply	other threads:[~2026-05-07 12:05 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-30 14:10 [PATCH container/manager v2 0/7] implement per-mountpoint uid/gid mapping Filip Schauer
2026-03-30 14:10 ` [PATCH container v2 1/7] namespaces: relax prototype of run_in_userns Filip Schauer
2026-03-30 14:10 ` [PATCH container v2 2/7] namespaces: refactor run_in_userns Filip Schauer
2026-03-30 14:10 ` [PATCH container v2 3/7] d/control: update versioned dependency for libpve-common-perl Filip Schauer
2026-03-30 14:10 ` [PATCH container v2 4/7] namespaces: add helper to create user namespace from idmap Filip Schauer
2026-03-30 14:10 ` [PATCH container v2 5/7] implement per-mountpoint uid/gid mapping Filip Schauer
2026-03-30 14:10 ` [PATCH manager v2 6/7] ui: lxc/MPEdit: remove duplicate "mp" assignment Filip Schauer
2026-03-30 14:10 ` [PATCH manager v2 7/7] ui: lxc/MPEdit: add "idmap" option Filip Schauer
2026-05-07 12:04   ` Dominik Csapak [this message]
2026-05-07 10:16 ` partially-applied: [PATCH container/manager v2 0/7] implement per-mountpoint uid/gid mapping Wolfgang Bumiller

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=82770e3a-4e89-4b6c-9ff9-b0c9ee097947@proxmox.com \
    --to=d.csapak@proxmox.com \
    --cc=f.schauer@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal