From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id 705741FF0B4 for ; Tue, 10 Nov 2026 16:05:47 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id E3AAD2162C; Tue, 10 Nov 2026 16:05:24 +0100 (CET) Mime-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=UTF-8 Date: Tue, 10 Nov 2026 16:05:12 +0100 Message-Id: Subject: Re: [PATCH manager 6/9] ui: dc options: allow editing the API token policy To: "Thomas Lamprecht" , X-Mailer: aerc 0.20.0 References: <20260923210000.4031318-1-t.lamprecht@proxmox.com> <20260923210000.4031318-7-t.lamprecht@proxmox.com> In-Reply-To: <20260923210000.4031318-7-t.lamprecht@proxmox.com> From: "Shannon Sterz" X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1794323113065 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.633 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust 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: 2K6DJMM6QRGQADQC3TJM7VBFLFYCG5ZX X-Message-ID-Hash: 2K6DJMM6QRGQADQC3TJM7VBFLFYCG5ZX X-MailFrom: s.sterz@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: On Wed Sep 23, 2026 at 10:59 PM CEST, Thomas Lamprecht wrote: > Render and edit the new datacenter token-policy option. The maximum > lifetime is entered in days, fractional values are allowed for > sub-day lifetimes, and the exact stored value is kept when the field > is left untouched, so a finer grained value set via the API survives > unrelated edits. > > Signed-off-by: Thomas Lamprecht > --- > www/manager6/dc/OptionView.js | 107 ++++++++++++++++++++++++++++++++++ > 1 file changed, 107 insertions(+) > > diff --git a/www/manager6/dc/OptionView.js b/www/manager6/dc/OptionView.j= s > index dc12aa7e1..595a8f070 100644 > --- a/www/manager6/dc/OptionView.js > +++ b/www/manager6/dc/OptionView.js > @@ -384,6 +384,113 @@ Ext.define('PVE.dc.OptionView', { > }, > ], > }); > + me.rows['token-policy'] =3D { > + required: true, > + header: gettext('API Token Policy'), > + renderer: function (policy) { > + if (!policy) { > + return Proxmox.Utils.NoneText; > + } > + let parts =3D []; > + if (Number(policy['require-expiry'])) { > + parts.push(gettext('Require Expiration Date')); nit: to visually indicate that max-lifetime always implies require-expiry, this field could check if a max-lifetime is set and be set to ticked and disabled if it is. > + } > + if (policy['max-lifetime'] !=3D=3D undefined) { > + let lifetime =3D Proxmox.Utils.format_duration_human= ( > + Number(policy['max-lifetime']), > + ); > + parts.push(Ext.String.format(gettext('Max. Lifetime:= {0}'), lifetime)); > + } > + if (Number(policy['disallow-expiry-changes'])) { > + parts.push(gettext('Disallow Expiration Changes')); > + } > + if (Number(policy['require-privilege-separation'])) { > + parts.push(gettext('Require Privilege Separation')); > + } > + return parts.length ? parts.join(', ') : Proxmox.Utils.N= oneText; > + }, > + editor: { > + xtype: 'proxmoxWindowEdit', > + subject: gettext('API Token Policy'), > + onlineHelp: 'pveum_token_policy', > + url: '/api2/extjs/cluster/options', > + fieldDefaults: { > + labelWidth: 150, > + }, > + setValues: function (values) { > + let me =3D this; > + // work on a copy, max-lifetime is stored in seconds= but entered in days, > + // fractional values are allowed; round up at two de= cimals for display and > + // remember the exact stored value so that saving wi= th the field untouched > + // does not alter it > + let policy =3D Ext.apply({}, values['token-policy'] = || {}); > + me.storedMaxLifetime =3D policy['max-lifetime']; > + if (policy['max-lifetime'] !=3D=3D undefined) { > + policy['max-lifetime'] =3D > + Math.ceil((Number(policy['max-lifetime']) / = 86400) * 100) / 100; > + } > + Ext.Array.each(me.query('inputpanel'), (panel) =3D> = panel.setValues(policy)); > + }, > + items: [ > + { > + xtype: 'inputpanel', > + onGetValues: function (values) { > + let me =3D this; > + let win =3D me.up('proxmoxWindowEdit'); > + let policy =3D {}; > + if (values['require-expiry']) { > + policy['require-expiry'] =3D 1; > + } > + if (values['disallow-expiry-changes']) { > + policy['disallow-expiry-changes'] =3D 1; > + } > + if (values['require-privilege-separation']) = { > + policy['require-privilege-separation'] = =3D 1; > + } > + let days =3D values['max-lifetime']; > + if (days !=3D=3D undefined && days !=3D=3D n= ull && days !=3D=3D '') { > + let untouched =3D !me.down('field[name= =3Dmax-lifetime]').isDirty(); > + policy['max-lifetime'] =3D > + untouched && win.storedMaxLifetime != =3D=3D undefined > + ? win.storedMaxLifetime > + : Math.round(Number(days) * 8640= 0); > + } > + if (Object.keys(policy).length =3D=3D=3D 0) = { > + return { delete: 'token-policy' }; > + } > + return { 'token-policy': PVE.Parser.printPro= pertyString(policy) }; > + }, > + items: [ > + { > + xtype: 'proxmoxcheckbox', > + name: 'require-expiry', > + uncheckedValue: 0, > + fieldLabel: gettext('Require Expiration = Date'), > + }, > + { > + xtype: 'numberfield', > + name: 'max-lifetime', > + minValue: 0.01, > + emptyText: gettext('No limit'), > + fieldLabel: gettext('Maximum Lifetime (d= ays)'), > + }, > + { > + xtype: 'proxmoxcheckbox', > + name: 'disallow-expiry-changes', > + uncheckedValue: 0, > + fieldLabel: gettext('Disallow Expiration= Changes'), > + }, > + { > + xtype: 'proxmoxcheckbox', > + name: 'require-privilege-separation', > + uncheckedValue: 0, > + fieldLabel: gettext('Require Privilege S= eparation'), > + }, > + ], > + }, > + ], > + }, > + }; > me.rows['tag-style'] =3D { > required: true, > renderer: (value) =3D> {