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 v3 3/3] ui: lxc/MPEdit: add "idmap" option
Date: Tue, 12 May 2026 16:01:21 +0200	[thread overview]
Message-ID: <20260512140126.171755-4-f.schauer@proxmox.com> (raw)
In-Reply-To: <20260512140126.171755-1-f.schauer@proxmox.com>

Integrate UID/GID mapping for container mount points into the web UI.

Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
 www/manager6/Makefile          |   1 +
 www/manager6/lxc/IdMapField.js | 187 +++++++++++++++++++++++++++++++++
 www/manager6/lxc/MPEdit.js     |   8 ++
 3 files changed, 196 insertions(+)
 create mode 100644 www/manager6/lxc/IdMapField.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index f63437d6..85d973fe 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -210,6 +210,7 @@ JSSRC= 							\
 	lxc/DNS.js					\
 	lxc/FeaturesEdit.js				\
 	lxc/EnvEdit.js				\
+	lxc/IdMapField.js				\
 	lxc/MPEdit.js					\
 	lxc/MPResize.js					\
 	lxc/Network.js					\
diff --git a/www/manager6/lxc/IdMapField.js b/www/manager6/lxc/IdMapField.js
new file mode 100644
index 00000000..93731ae2
--- /dev/null
+++ b/www/manager6/lxc/IdMapField.js
@@ -0,0 +1,187 @@
+Ext.define('PVE.lxc.IdMapField', {
+    extend: 'Ext.form.FieldContainer',
+    xtype: 'pveLxcIdMapField',
+
+    layout: { type: 'vbox', align: 'stretch' },
+
+    controller: {
+        xclass: 'Ext.app.ViewController',
+
+        control: {
+            'grid proxmoxintegerfield,grid proxmoxKVComboBox': {
+                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();
+                },
+            },
+        },
+
+        onIdMapFieldChange: function (field, value) {
+            let me = this;
+            let passthrough = value === 'passthrough';
+            let checkbox = me.lookup('passthrough');
+            checkbox.suspendEvent('change');
+            checkbox.setValue(passthrough);
+            checkbox.resumeEvent('change');
+            me.lookup('idmaps').setVisible(!passthrough);
+            me.lookup('addIdmapButton').setVisible(!passthrough);
+
+            let store = me.lookup('idmaps').getStore();
+            if (!passthrough && value) {
+                store.setData(
+                    value.split(';').map((v) => {
+                        let [type, ct, host, length] = v.split(':');
+                        return { type, ct, host, length };
+                    }),
+                );
+            } else {
+                store.removeAll();
+            }
+        },
+
+        onPassthroughCheckboxChange: function (checkbox, checked) {
+            let me = this;
+            me.lookup('idmap').setValue(checked ? 'passthrough' : '');
+        },
+
+        addIdMap: function () {
+            let me = this;
+            me.lookup('idmaps').getStore().add({ type: 'u', ct: '', host: '', length: '' });
+            me.updateIdMapField();
+        },
+
+        removeIdMap: function (button) {
+            let me = this;
+            me.lookup('idmaps').getStore().remove(button.getWidgetRecord());
+            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');
+        },
+    },
+
+    items: [
+        {
+            xtype: 'proxmoxcheckbox',
+            reference: 'passthrough',
+            fieldLabel: gettext('ID Mapping'),
+            boxLabel: gettext('Passthrough'),
+            isFormField: false,
+            listeners: {
+                change: 'onPassthroughCheckboxChange',
+            },
+        },
+        {
+            xtype: 'grid',
+            height: 170,
+            scrollable: true,
+            reference: 'idmaps',
+            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: 'proxmoxKVComboBox',
+                        margin: '4 0',
+                        allowBlank: false,
+                        comboItems: [
+                            ['u', 'UID'],
+                            ['g', 'GID'],
+                        ],
+                    },
+                    flex: 1,
+                },
+                {
+                    text: gettext('Container'),
+                    xtype: 'widgetcolumn',
+                    dataIndex: 'ct',
+                    widget: {
+                        xtype: 'proxmoxintegerfield',
+                        margin: '4 0',
+                        emptyText: gettext('Container'),
+                        allowBlank: false,
+                        minValue: 0,
+                    },
+                    flex: 1,
+                },
+                {
+                    text: gettext('Host'),
+                    xtype: 'widgetcolumn',
+                    dataIndex: 'host',
+                    widget: {
+                        xtype: 'proxmoxintegerfield',
+                        margin: '4 0',
+                        emptyText: gettext('Host'),
+                        allowBlank: false,
+                        minValue: 0,
+                    },
+                    flex: 1,
+                },
+                {
+                    text: gettext('Range Size'),
+                    xtype: 'widgetcolumn',
+                    dataIndex: 'length',
+                    widget: {
+                        xtype: 'proxmoxintegerfield',
+                        margin: '4 0',
+                        emptyText: gettext('Range Size'),
+                        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',
+        },
+        {
+            xtype: 'hidden',
+            reference: 'idmap',
+            name: 'idmap',
+            listeners: {
+                change: 'onIdMapFieldChange',
+            },
+        },
+    ],
+});
diff --git a/www/manager6/lxc/MPEdit.js b/www/manager6/lxc/MPEdit.js
index b1f67741..b193ff89 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);
@@ -353,6 +354,13 @@ Ext.define('PVE.lxc.MountPointInputPanel', {
             },
         },
     ],
+
+    advancedColumnB: [
+        {
+            xtype: 'pveLxcIdMapField',
+            name: 'idmap',
+        },
+    ],
 });
 
 Ext.define('PVE.lxc.MountPointEdit', {
-- 
2.47.3





  parent reply	other threads:[~2026-05-12 14:03 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-12 14:01 [PATCH manager v3 0/3] ui: add container mount point idmapping Filip Schauer
2026-05-12 14:01 ` [PATCH manager v3 1/3] ui: lxc/MPEdit: remove duplicate "mp" assignment Filip Schauer
2026-05-12 14:01 ` [PATCH manager v3 2/3] d/control: bump versioned dependency for pve-container Filip Schauer
2026-05-12 14:01 ` Filip Schauer [this message]
2026-05-12 14:57   ` [PATCH manager v3 3/3] ui: lxc/MPEdit: add "idmap" option Wolfgang Bumiller
2026-05-13  9:33     ` superseded: " Filip Schauer

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=20260512140126.171755-4-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