From: "Max R. Carrara" <m.carrara@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH proxmox-widget-toolkit 09/13] utils: introduce helper function getFieldDefFromPropertySchema
Date: Tue, 23 Jun 2026 16:33:26 +0200 [thread overview]
Message-ID: <20260623143402.772452-10-m.carrara@proxmox.com> (raw)
In-Reply-To: <20260623143402.772452-1-m.carrara@proxmox.com>
This helper takes the JSON schema of a single property and transforms
it into an object representing an Ext JS field.
Currently, four types for properties are supported: `string`,
`integer`, `number` and `boolean`. Many JSON schema validation
keywords are also supported and converted to Ext JS analogues.
Types --> Ext JS field types:
- boolean --> proxmoxcheckbox
- integer --> proxmoxintegerfield
- number --> numberfield
- string --> proxmoxtextfield
Keywords --> Ext JS properties / behavior:
- minLength --> minLength
- maxLength --> maxLength
- minimum --> minValue
- maximum --> maxValue
- title --> fieldLabel (using the field's name if no title)
- optional --> allowBlank
- description --> shown on hover
- enum --> use proxmoxKVComboBox as field instead
- default --> set as `value` of the field and use as `emptyText`
Furthermore, add support for the `multiline` JSON schema `format`.
This means that any property with the `multiline` format becomes a
`proxmoxtextarea` field.
Additionally, the helper also takes "extra attributes" and a "context"
which allows for more fine-grained control over how the ExtJS field is
actually constructed and also makes it possible to express different
UI-specific behaviors that cannot be modeled via JSONSchema.
The only variable in the context is `isCreate`, which is used to hint
whether the field is used when creating a record or not.
The following extra attributes are added:
- sensitive
Marks a field as "sensitive", which is useful for password fields
for example. This causes most fields to become `proxmoxtextfield`s
instead, with their inputs obfuscated.
Fields with the `multiline` format are kept as `proxmoxtextarea` to
allow copy-pasting private keys into the field or similar.
- readonly
Causes a field to become a `displayfield` instead. Boolean fields
are rendered as "Yes / No" (locale-aware).
Signed-off-by: Max R. Carrara <m.carrara@proxmox.com>
---
src/Utils.js | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 150 insertions(+)
diff --git a/src/Utils.js b/src/Utils.js
index 14a90bf..f41080f 100644
--- a/src/Utils.js
+++ b/src/Utils.js
@@ -1600,6 +1600,156 @@ Ext.define('Proxmox.Utils', {
}
return degrees;
},
+
+ /**
+ * Takes a JSONSchema of a single property and transforms it into an
+ * object representing an ExtJS field.
+ *
+ * @param {string} fieldName - The value for the `name` property of the resulting object.
+ * @param {Object} propSchema - The JSON schema of a property.
+ * @param {Object} extraAttributes - Additional attributes describing the
+ * field which do not exist as keywords in JSON schema.
+ * Note that none of these attributes represent any specific ExtJS property.
+ * @param {Boolean} extraAttributes.readonly - Whether the field shall be read-only.
+ * "Read-only" here means that the field's `xtype` will be set to
+ * `'displayfield'`, regardless of what data type the field will
+ * display. Additionally, checkboxes (booleans) will be displayed as
+ * "Yes" or "No" depending on their value (locale-aware).
+ * @param {Boolean} extraAttributes.sensitive - Whether the field
+ * contains sensitive data, such as a password or similar.
+ * @param {Object} context - The context within which the field is being used.
+ * @param {Boolean} context.isCreate - Whether the field is used when creating a record.
+ */
+ getFieldDefFromPropertySchema: function (fieldName, propSchema, extraAttributes, context) {
+ const fieldPropsByType = {
+ string: {
+ xtype: 'proxmoxtextfield',
+ minLength: propSchema.minLength,
+ maxLength: propSchema.maxLength,
+ },
+ integer: {
+ xtype: 'proxmoxintegerfield',
+ minValue: propSchema.minimum,
+ maxValue: propSchema.maximum,
+ },
+ number: {
+ xtype: 'numberfield',
+ minValue: propSchema.minimum,
+ maxValue: propSchema.maximum,
+ },
+ boolean: {
+ xtype: 'proxmoxcheckbox',
+ uncheckedValue: 0,
+ },
+ };
+
+ const commonFieldProps = {
+ name: fieldName,
+ fieldLabel: Ext.htmlEncode(fieldName),
+ emptyText: propSchema.default || '',
+ allowBlank: propSchema.optional,
+ };
+
+ const fieldPropsByFormat = {
+ multiline: {
+ xtype: 'proxmoxtextarea',
+ },
+ };
+
+ const anyToBool = (value) => {
+ if (typeof value === 'string' || value instanceof String) {
+ value = value.trim().toLowerCase();
+ return ['y', 'yes', 'true'].includes(value);
+ }
+
+ return Boolean(value);
+ };
+
+ let fieldProps = fieldPropsByType[propSchema.type];
+ if (fieldProps === undefined) {
+ console.warn(`Unhandled property type '${propSchema.type}'`);
+ fieldProps = fieldPropsByType.string;
+ }
+
+ let formatFieldProps = fieldPropsByFormat[propSchema.format] ?? {};
+
+ let field = {
+ ...fieldProps,
+ ...commonFieldProps,
+ ...formatFieldProps,
+ };
+
+ if (propSchema.title) {
+ field.fieldLabel = Ext.htmlEncode(propSchema.title);
+ }
+
+ if (propSchema.description) {
+ field.autoEl = {
+ tag: 'div',
+ 'data-qtip': Ext.htmlEncode(Ext.htmlEncode(propSchema.description)),
+ };
+ }
+
+ if (propSchema.enum) {
+ const comboBoxProps = {
+ xtype: 'proxmoxKVComboBox',
+ comboItems: propSchema.enum.map((item) => [item, Ext.htmlEncode(item)]),
+ deleteEmpty: !context.isCreate,
+ editable: true,
+ };
+
+ Object.assign(field, comboBoxProps);
+ }
+
+ if (context.isCreate && propSchema.default !== undefined) {
+ if (propSchema.type === 'boolean') {
+ field.checked = anyToBool(propSchema.default);
+ }
+
+ if (field.xtype === 'proxmoxKVComboBox') {
+ let defaultText =
+ Proxmox.Utils.defaultText + ' (' + Ext.htmlEncode(propSchema.default) + ')';
+
+ field.comboItems.unshift(['__default__', defaultText]);
+ field.value = '__default__';
+ } else {
+ field.value = propSchema.default;
+ }
+ }
+
+ if (extraAttributes.sensitive) {
+ // textarea fields for multiline strings stay as-is, because
+ // Ext JS will turn them into <input> elements otherwise, which
+ // in turn causes newlines to be replaced with spaces.
+ // 'inputType' unfortunately has no effect for textarea fields.
+ if (field.xtype !== 'proxmoxtextarea') {
+ field.xtype = 'proxmoxtextfield';
+ field.inputType = 'password';
+ }
+
+ if (context.isCreate) {
+ field.value = '';
+ field.emptyText = Proxmox.Utils.NoneText;
+ } else {
+ field.emptyText = gettext('Unchanged');
+ }
+ }
+
+ if (extraAttributes.readonly) {
+ field.xtype = 'displayfield';
+
+ if (propSchema.type === 'boolean') {
+ field.renderer = Proxmox.Utils.format_boolean;
+ }
+
+ if (extraAttributes.sensitive) {
+ field.value = '*********';
+ field.emptyText = '';
+ }
+ }
+
+ return field;
+ },
},
singleton: true,
--
2.47.3
next prev parent reply other threads:[~2026-06-23 14:36 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-23 14:33 [PATCH common/manager/proxmox-widget-toolkit/storage 00/13] GUI Support for Custom Storage Plugins Max R. Carrara
2026-06-23 14:33 ` [PATCH pve-common 01/13] json schema: add multiline string format Max R. Carrara
2026-06-23 14:33 ` [PATCH pve-storage 02/13] api: plugins/storage: add initial routes and endpoints Max R. Carrara
2026-06-23 14:33 ` [PATCH pve-storage 03/13] api: plugins/storage/plugin: include schema in plugin metadata Max R. Carrara
2026-06-23 14:33 ` [PATCH pve-storage 04/13] api: plugins/storage/plugin: mark sensitive properties in schema Max R. Carrara
2026-06-23 14:33 ` [PATCH pve-storage 05/13] api: plugins/storage/plugin: factor plugin metadata code into helper Max R. Carrara
2026-06-23 14:33 ` [PATCH pve-storage 06/13] api: plugins/storage/plugin: add plugins' 'content' to their metadata Max R. Carrara
2026-06-23 14:33 ` [PATCH pve-storage 07/13] all plugins: add 'title' to properties, adapt 'description's Max R. Carrara
2026-06-23 14:33 ` [PATCH proxmox-widget-toolkit 08/13] form: introduce new 'proxmoxtextarea' field Max R. Carrara
2026-06-23 14:33 ` Max R. Carrara [this message]
2026-06-23 14:33 ` [PATCH proxmox-widget-toolkit 10/13] acme: use helper to construct ExtJS fields from property schemas Max R. Carrara
2026-06-23 14:33 ` [PATCH pve-manager 11/13] api: add API routes 'plugins' and 'plugins/storage' Max R. Carrara
2026-06-23 14:33 ` [PATCH pve-manager 12/13] ui: storage view: display error when no editor for storage type exists Max R. Carrara
2026-06-23 14:33 ` [PATCH pve-manager 13/13] ui: storage: add basic UI integration for custom storage plugins Max R. Carrara
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=20260623143402.772452-10-m.carrara@proxmox.com \
--to=m.carrara@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