From efcad405e5e5aee79bb9c8e92ed28ea892b0007a Mon Sep 17 00:00:00 2001 From: Kevin Adams Date: Sat, 23 May 2026 21:17:58 -0400 Subject: [PATCH] fix: sensitive-property handling + UI autofill + API key reveal button truenas_api_key must be optional in options() because PVE extracts sensitive-properties from the POST body before calling check_config, causing a spurious 'missing required option' 500 on storage create. Add a guard in _api() so a missing key produces a clear error. UI: add autocomplete="url" on host and "new-password" on API key to prevent Firefox/Chrome from filling in PVE login credentials. Add a reveal trigger button to show/hide the API key field. Co-Authored-By: Claude Sonnet 4.6 --- perl5/PVE/Storage/Custom/TrueNAS.pm | 5 ++++- ui/truenas-storage.js | 33 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/perl5/PVE/Storage/Custom/TrueNAS.pm b/perl5/PVE/Storage/Custom/TrueNAS.pm index 10a541f..24399ce 100644 --- a/perl5/PVE/Storage/Custom/TrueNAS.pm +++ b/perl5/PVE/Storage/Custom/TrueNAS.pm @@ -88,7 +88,7 @@ sub options { bwlimit => { optional => 1 }, shared => { optional => 1 }, truenas_host => { fixed => 1 }, - truenas_api_key => {}, + truenas_api_key => { optional => 1 }, truenas_ssl => { optional => 1 }, truenas_ssl_verify => { optional => 1 }, truenas_pool => { fixed => 1 }, @@ -128,6 +128,9 @@ sub _ua { sub _api { my ($scfg, $method, $path, $data) = @_; + die "TrueNAS API key is not configured for storage '$scfg->{truenas_host}'\n" + unless $scfg->{truenas_api_key}; + my $scheme = ($scfg->{truenas_ssl} // 1) ? 'https' : 'http'; my $url = "$scheme://$scfg->{truenas_host}/api/v2.0$path"; diff --git a/ui/truenas-storage.js b/ui/truenas-storage.js index fd89a4e..35fff11 100644 --- a/ui/truenas-storage.js +++ b/ui/truenas-storage.js @@ -5,6 +5,19 @@ // // Loaded by index.html.tpl after pvemanagerlib.js, so PVE.Utils is available. +// Inject CSS for the API key reveal trigger (eye icon via Font Awesome) +(function () { + let style = document.createElement('style'); + style.textContent = [ + '.truenas-reveal-trigger::before {', + ' font-family: "Font Awesome 5 Free";', + ' font-weight: 900;', + ' content: "\\f06e";', // fa-eye + '}', + ].join(''); + document.head.appendChild(style); +}()); + // Register TrueNAS in the storage type dropdown PVE.Utils.storageSchema.truenas = { name: 'TrueNAS (ZFS/iSCSI)', @@ -26,6 +39,9 @@ Ext.define('PVE.storage.TrueNASInputPanel', { fieldLabel: gettext('TrueNAS Host'), name: 'truenas_host', allowBlank: false, + autoComplete: false, + // 'url' misdirects browser autofill away from username heuristics + inputAttrTpl: 'autocomplete="url"', emptyText: gettext('hostname or IP address'), }, { @@ -34,9 +50,22 @@ Ext.define('PVE.storage.TrueNASInputPanel', { name: 'truenas_api_key', inputType: 'password', allowBlank: !me.isCreate, + autoComplete: false, + // 'new-password' prevents browser from filling a saved password here + inputAttrTpl: 'autocomplete="new-password"', emptyText: me.isCreate ? gettext('Paste API key from TrueNAS UI') : gettext('unchanged — paste new key to change'), + triggers: { + reveal: { + cls: 'truenas-reveal-trigger', + tooltip: gettext('Show / hide API key'), + handler: function (field) { + let dom = field.inputEl.dom; + dom.type = dom.type === 'password' ? 'text' : 'password'; + }, + }, + }, listeners: { // On edit: don't submit unless the user types a new value afterrender: function (field) { @@ -56,6 +85,7 @@ Ext.define('PVE.storage.TrueNASInputPanel', { fieldLabel: gettext('Pool'), name: 'truenas_pool', allowBlank: false, + autoComplete: false, emptyText: gettext('ZFS pool name (e.g. tank)'), }, { @@ -63,6 +93,7 @@ Ext.define('PVE.storage.TrueNASInputPanel', { fieldLabel: gettext('Dataset'), name: 'truenas_dataset', allowBlank: true, + autoComplete: false, emptyText: gettext('Optional (e.g. proxmox)'), deleteEmpty: !me.isCreate, }, @@ -90,6 +121,7 @@ Ext.define('PVE.storage.TrueNASInputPanel', { fieldLabel: gettext('Portal IP'), name: 'truenas_portal_ip', allowBlank: true, + autoComplete: false, emptyText: gettext('Optional — defaults to TrueNAS host'), deleteEmpty: !me.isCreate, }, @@ -98,6 +130,7 @@ Ext.define('PVE.storage.TrueNASInputPanel', { fieldLabel: gettext('Target IQN'), name: 'truenas_target', allowBlank: true, + autoComplete: false, emptyText: gettext('Optional — auto-discovered if blank'), deleteEmpty: !me.isCreate, },