Add CustomBase.js, a copy of Base.js specifically for custom form
views of storage plugin configs.

While there is a large overlap between the files' contents, they are
still kept separate for the purposes of this RFC. This makes it
easier to differ between how custom storage plugins and inbuilt
storage plugins are handled in the GUI at the moment, until this idea
has been fleshed out more.

The main UI building logic is in `PVE.storage.CustomInputPanel`. Right
now, there are no custom fields or anything of the sort; the field's
Ext.JS code is simply stitched together piece by piece depending on
the form view definition provided.

The fields for the 'storage', 'content', 'nodes' and 'disable'
('enable') are always included in every form view and cannot be
disabled at the moment, as they exist in virtually every storage
plugin.

Signed-off-by: Max R. Carrara <m.carr...@proxmox.com>
---
 www/manager6/Makefile              |   1 +
 www/manager6/storage/CustomBase.js | 402 +++++++++++++++++++++++++++++
 2 files changed, 403 insertions(+)
 create mode 100644 www/manager6/storage/CustomBase.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 85f9268d..a329d36e 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -326,6 +326,7 @@ JSSRC=                                                      
\
        storage/ContentView.js                          \
        storage/BackupView.js                           \
        storage/Base.js                                 \
+       storage/CustomBase.js                           \
        storage/Browser.js                              \
        storage/CIFSEdit.js                             \
        storage/CephFSEdit.js                           \
diff --git a/www/manager6/storage/CustomBase.js 
b/www/manager6/storage/CustomBase.js
new file mode 100644
index 00000000..9ee2417c
--- /dev/null
+++ b/www/manager6/storage/CustomBase.js
@@ -0,0 +1,402 @@
+Ext.define('PVE.panel.CustomStorageBase', {
+    extend: 'Proxmox.panel.InputPanel',
+    controller: 'storageEdit',
+
+    type: '',
+
+    onGetValues: function (values) {
+        let me = this;
+
+        if (me.isCreate) {
+            values.type = me.type;
+        } else {
+            delete values.storage;
+        }
+
+        values.disable = values.enable ? 0 : 1;
+        delete values.enable;
+
+        return values;
+    },
+
+    initComponent: function () {
+        let me = this;
+
+        me.column1.unshift(
+            {
+                xtype: me.isCreate ? 'textfield' : 'displayfield',
+                name: 'storage',
+                value: me.storageId || '',
+                fieldLabel: 'ID',
+                vtype: 'StorageId',
+                allowBlank: false,
+            },
+            {
+                xtype: 'pveContentTypeSelector',
+                cts: me.metadataForPlugin.content.supported,
+                fieldLabel: gettext('Content'),
+                name: 'content',
+                value: me.metadataForPlugin.content.default,
+                multiSelect: true,
+                allowBlank: false,
+            },
+        );
+
+        if (!me.column2) {
+            me.column2 = [];
+        }
+
+        me.column2.unshift(
+            {
+                xtype: 'pveNodeSelector',
+                name: 'nodes',
+                reference: 'storageNodeRestriction',
+                disabled: me.storageId === 'local',
+                fieldLabel: gettext('Nodes'),
+                emptyText: gettext('All') + ' (' + gettext('No restrictions') 
+ ')',
+                multiSelect: true,
+                autoSelect: false,
+            },
+            {
+                xtype: 'proxmoxcheckbox',
+                name: 'enable',
+                checked: true,
+                uncheckedValue: 0,
+                fieldLabel: gettext('Enable'),
+            },
+        );
+
+        me.callParent();
+    },
+});
+
+Ext.define('PVE.storage.CustomBaseEdit', {
+    extend: 'Proxmox.window.Edit',
+
+    apiCallDone: function (success, response, options) {
+        let me = this;
+        if (typeof me.ipanel.apiCallDone === 'function') {
+            me.ipanel.apiCallDone(success, response, options);
+        }
+    },
+
+    initComponent: function () {
+        let me = this;
+
+        me.isCreate = !me.storageId;
+
+        if (me.isCreate) {
+            me.url = '/api2/extjs/storage';
+            me.method = 'POST';
+        } else {
+            me.url = '/api2/extjs/storage/' + me.storageId;
+            me.method = 'PUT';
+        }
+
+        me.ipanel = Ext.create(me.paneltype, {
+            title: gettext('General'),
+            type: me.type,
+            isCreate: me.isCreate,
+            storageId: me.storageId,
+            formView: me.formView,
+            metadataForPlugin: me.metadataForPlugin,
+        });
+
+        let subject = me.metadataForPlugin['short-name'] || 
PVE.Utils.format_storage_type(me.type);
+
+        Ext.apply(me, {
+            subject: subject,
+            isAdd: true,
+            bodyPadding: 0,
+            items: {
+                xtype: 'tabpanel',
+                region: 'center',
+                layout: 'fit',
+                bodyPadding: 10,
+                items: [
+                    me.ipanel,
+                    {
+                        xtype: 'pveBackupJobPrunePanel',
+                        title: gettext('Backup Retention'),
+                        hasMaxProtected: true,
+                        isCreate: me.isCreate,
+                        keepAllDefaultForCreate: true,
+                        showPBSHint: me.ipanel.isPBS,
+                        fallbackHintHtml: gettext(
+                            "Without any keep option, the node's vzdump.conf 
or `keep-all` is used as fallback for backup jobs",
+                        ),
+                    },
+                ],
+            },
+        });
+
+        if (me.ipanel.extraTabs) {
+            me.ipanel.extraTabs.forEach((panel) => {
+                panel.isCreate = me.isCreate;
+                me.items.items.push(panel);
+            });
+        }
+
+        me.callParent();
+
+        if (!me.canDoBackups) {
+            // cannot mask now, not fully rendered until activated
+            me.down('pmxPruneInputPanel').needMask = true;
+        }
+
+        if (!me.isCreate) {
+            me.load({
+                success: function (response, options) {
+                    let values = response.result.data;
+                    let ctypes = values.content || '';
+
+                    values.content = ctypes.split(',');
+
+                    if (values.nodes) {
+                        values.nodes = values.nodes.split(',');
+                    }
+                    values.enable = values.disable ? 0 : 1;
+                    if (values['prune-backups']) {
+                        let retention = 
PVE.Parser.parsePropertyString(values['prune-backups']);
+                        delete values['prune-backups'];
+                        Object.assign(values, retention);
+                    }
+
+                    me.query('inputpanel').forEach((panel) => {
+                        panel.setValues(values);
+                    });
+                },
+            });
+        }
+    },
+});
+
+Ext.define('PVE.storage.CustomInputPanel', {
+    extend: 'PVE.panel.CustomStorageBase',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    onlineHelp: '',
+
+    buildFieldFromDefinition: function (me, fieldDef) {
+        let { property, label, attributes } = fieldDef;
+
+        if (property in me.visitedStorageProperties) {
+            throw (
+                `duplicate property '${property}' in form view` +
+                ` for custom storage plugin '${me.type}'`
+            );
+        }
+
+        me.visitedStorageProperties[property] = 1;
+
+        let field = {
+            name: property,
+            fieldLabel: label,
+            cbind: {},
+        };
+
+        switch (fieldDef['field-type']) {
+            case 'boolean':
+                field.xtype = 'proxmoxcheckbox';
+                field.uncheckedValue = 0;
+                break;
+
+            case 'integer':
+                field.xtype = 'proxmoxintegerfield';
+                break;
+
+            case 'number':
+                field.xtype = 'numberfield';
+                break;
+
+            case 'string':
+                switch (attributes['display-mode']) {
+                    case 'text':
+                        field.xtype = 'textfield';
+                        break;
+                    case 'textarea':
+                        field.xtype = 'textarea';
+                        break;
+                    case 'password':
+                        field.xtype = 'proxmoxtextfield';
+                        field.inputType = 'password';
+                        break;
+                    default:
+                        field.xtype = 'textfield';
+                }
+
+                break;
+
+            case 'selection':
+                field.xtype = 'proxmoxKVComboBox';
+                field.comboItems = attributes['selection-values'] || [];
+                field.autoSelect = true;
+
+                if (me.isCreate) {
+                    let firstPair = attributes['selection-values'][0];
+                    if (firstPair) {
+                        field.value = firstPair[0];
+                    }
+                }
+
+                switch (attributes['selection-mode']) {
+                    case 'single':
+                        field.multiSelect = false;
+                        break;
+                    case 'multi':
+                        field.multiSelect = true;
+                        break;
+                    case 'default':
+                        field.multiSelect = false;
+                }
+
+                break;
+
+            default:
+                field.xtype = 'displayfield';
+                break;
+        }
+
+        // **Common Attributes**
+        // required
+        if (attributes.required) {
+            field.allowBlank = false;
+        }
+
+        // readonly
+        if (attributes.readonly) {
+            switch (fieldDef['field-type']) {
+                case 'boolean':
+                    field.disabled = true;
+                    break;
+                case 'integer':
+                    field.xtype = 'displayfield';
+                    break;
+                case 'number':
+                    field.xtype = 'displayfield';
+                    break;
+                case 'string':
+                    field.xtype = 'displayfield';
+                    break;
+                case 'selection':
+                    field.xtype = 'displayfield';
+                    break;
+            }
+        }
+
+        // default
+        if (attributes.default && me.isCreate) {
+            switch (fieldDef['field-type']) {
+                case 'boolean':
+                    field.value = Boolean(attributes.default);
+                    field.checked = Boolean(attributes.default);
+                    break;
+
+                case 'integer':
+                    field.value = Number(attributes.default);
+                    break;
+
+                case 'number':
+                    field.value = Number(attributes.default);
+                    break;
+
+                case 'string':
+                    field.value = attributes.default;
+                    break;
+
+                case 'selection':
+                    switch (attributes['selection-mode']) {
+                        case 'single':
+                            field.value = attributes.default[0];
+                            break;
+
+                        case 'multi':
+                            field.value = attributes.default;
+                            break;
+
+                        default:
+                            field.value = attributes.default[0];
+                    }
+                    break;
+            }
+        }
+
+        return field;
+    },
+
+    buildColumnFromDefinition: function (me, columnDef) {
+        return columnDef.fields.map((fieldDef) => 
me.buildFieldFromDefinition(me, fieldDef));
+    },
+
+    initComponent: function () {
+        let me = this;
+
+        // TODO: take schema version into account
+
+        me.visitedStorageProperties = {
+            storage: 1,
+            content: 1,
+            notes: 1,
+            disable: 1,
+            enable: 1,
+        };
+
+        const viewDef = me.formView.definition.general;
+        const maxColumns = 2;
+        const maxAdvancedColumns = 2;
+
+        let columns = viewDef.columns ?? [];
+        let columnBottom = viewDef['column-bottom'];
+        let advancedColumns = viewDef['columns-advanced'] ?? [];
+        let advancedColumnBottom = viewDef['column-advanced-bottom'];
+
+        let columnCount = Math.min(columns.length, maxColumns);
+
+        let advancedColumnCount = Math.min(advancedColumns.length, 
maxAdvancedColumns);
+
+        try {
+            columns.slice(0, columnCount).map((columnDef, index) => {
+                let colName = 'column' + (index + 1);
+
+                if (!me[colName]) {
+                    me[colName] = [];
+                }
+
+                me[colName] = 
me[colName].concat(me.buildColumnFromDefinition(me, columnDef));
+            });
+
+            if (columnBottom) {
+                if (!me.columnB) {
+                    me.columnB = [];
+                }
+
+                me.columnB = 
me.columnB.concat(me.buildColumnFromDefinition(me, columnBottom));
+            }
+
+            advancedColumns.slice(0, advancedColumnCount).map((columnDef, 
index) => {
+                let colName = 'advancedColumn' + (index + 1);
+
+                if (!me[colName]) {
+                    me[colName] = [];
+                }
+
+                me[colName] = 
me[colName].concat(me.buildColumnFromDefinition(me, columnDef));
+            });
+
+            if (advancedColumnBottom) {
+                if (!me.advancedColumnB) {
+                    me.advancedColumnB = [];
+                }
+
+                me.advancedColumnB = me.advancedColumnB.concat(
+                    me.buildColumnFromDefinition(me, advancedColumnBottom),
+                );
+            }
+        } catch (error) {
+            Ext.Msg.alert(gettext('Error'), error);
+            return;
+        }
+
+        me.callParent();
+    },
+});
-- 
2.47.2



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to