public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Filip Schauer <f.schauer@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH manager v2 7/7] ui: lxc/MPEdit: add "idmap" option
Date: Mon, 30 Mar 2026 16:10:18 +0200	[thread overview]
Message-ID: <20260330141021.151921-8-f.schauer@proxmox.com> (raw)
In-Reply-To: <20260330141021.151921-1-f.schauer@proxmox.com>

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', {
-- 
2.47.3





      parent reply	other threads:[~2026-03-30 14:13 UTC|newest]

Thread overview: 8+ 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 ` Filip Schauer [this message]

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=20260330141021.151921-8-f.schauer@proxmox.com \
    --to=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