[pmg-devel] [PATCH v2 widget-toolkit 6/7] add ACME plugin editing

Wolfgang Bumiller w.bumiller at proxmox.com
Fri Mar 12 16:24:13 CET 2021


Like with the account panel, the 'acmeUrl' base needs to be
specified, otherwise this is copied from PVE

Signed-off-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
---
* No changes since v1

 src/Makefile                 |   2 +
 src/panel/ACMEPlugin.js      | 116 +++++++++++++++++
 src/window/ACMEPluginEdit.js | 242 +++++++++++++++++++++++++++++++++++
 3 files changed, 360 insertions(+)
 create mode 100644 src/panel/ACMEPlugin.js
 create mode 100644 src/window/ACMEPluginEdit.js

diff --git a/src/Makefile b/src/Makefile
index 00a25c7..0e1fb45 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -51,6 +51,7 @@ JSSRC=					\
 	panel/GaugeWidget.js		\
 	panel/Certificates.js		\
 	panel/ACMEAccount.js		\
+	panel/ACMEPlugin.js		\
 	window/Edit.js			\
 	window/PasswordEdit.js		\
 	window/SafeDestroy.js		\
@@ -60,6 +61,7 @@ JSSRC=					\
 	window/ZFSDetail.js		\
 	window/Certificates.js		\
 	window/ACMEAccount.js		\
+	window/ACMEPluginEdit.js	\
 	node/APT.js			\
 	node/NetworkEdit.js		\
 	node/NetworkView.js		\
diff --git a/src/panel/ACMEPlugin.js b/src/panel/ACMEPlugin.js
new file mode 100644
index 0000000..ca58106
--- /dev/null
+++ b/src/panel/ACMEPlugin.js
@@ -0,0 +1,116 @@
+Ext.define('Proxmox.panel.ACMEPluginView', {
+    extend: 'Ext.grid.Panel',
+    alias: 'widget.pmxACMEPluginView',
+
+    title: gettext('Challenge Plugins'),
+    acmeUrl: undefined,
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	addPlugin: function() {
+	    let me = this;
+	    let view = me.getView();
+	    Ext.create('Proxmox.window.ACMEPluginEdit', {
+		acmeUrl: view.acmeUrl,
+		url: `${view.acmeUrl}/plugins`,
+		isCreate: true,
+		apiCallDone: function() {
+		    me.reload();
+		},
+	    }).show();
+	},
+
+	editPlugin: function() {
+	    let me = this;
+	    let view = me.getView();
+	    let selection = view.getSelection();
+	    if (selection.length < 1) return;
+	    let plugin = selection[0].data.plugin;
+	    Ext.create('Proxmox.window.ACMEPluginEdit', {
+		acmeUrl: view.acmeUrl,
+		url: `${view.acmeUrl}/plugins/${plugin}`,
+		apiCallDone: function() {
+		    me.reload();
+		},
+	    }).show();
+	},
+
+	reload: function() {
+	    let me = this;
+	    let view = me.getView();
+	    view.getStore().rstore.load();
+	},
+    },
+
+    minHeight: 150,
+    emptyText: gettext('No Plugins configured'),
+
+    columns: [
+	{
+	    dataIndex: 'plugin',
+	    text: gettext('Plugin'),
+	    renderer: Ext.String.htmlEncode,
+	    flex: 1,
+	},
+	{
+	    dataIndex: 'api',
+	    text: 'API',
+	    renderer: Ext.String.htmlEncode,
+	    flex: 1,
+	},
+    ],
+
+    listeners: {
+	itemdblclick: 'editPlugin',
+    },
+
+    store: {
+	type: 'diff',
+	autoDestroy: true,
+	autoDestroyRstore: true,
+	rstore: {
+	    type: 'update',
+	    storeid: 'proxmox-acme-plugins',
+	    model: 'proxmox-acme-plugins',
+	    autoStart: true,
+	    filters: item => !!item.data.api,
+	},
+	sorters: 'plugin',
+    },
+
+    initComponent: function() {
+	let me = this;
+
+	if (!me.acmeUrl) {
+	    throw "no acmeUrl given";
+	}
+	me.url = `${me.acmeUrl}/plugins`;
+
+	Ext.apply(me, {
+	    tbar: [
+		{
+		    xtype: 'proxmoxButton',
+		    text: gettext('Add'),
+		    handler: 'addPlugin',
+		    selModel: false,
+		},
+		{
+		    xtype: 'proxmoxButton',
+		    text: gettext('Edit'),
+		    handler: 'editPlugin',
+		    disabled: true,
+		},
+		{
+		    xtype: 'proxmoxStdRemoveButton',
+		    callback: 'reload',
+		    baseurl: `${me.acmeUrl}/plugins`,
+		},
+	    ],
+	});
+
+	me.callParent();
+
+	me.store.rstore.proxy.setUrl(`/api2/json/${me.acmeUrl}/plugins`);
+    },
+});
diff --git a/src/window/ACMEPluginEdit.js b/src/window/ACMEPluginEdit.js
new file mode 100644
index 0000000..237b362
--- /dev/null
+++ b/src/window/ACMEPluginEdit.js
@@ -0,0 +1,242 @@
+Ext.define('Proxmox.window.ACMEPluginEdit', {
+    extend: 'Proxmox.window.Edit',
+    xtype: 'pmxACMEPluginEdit',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    //onlineHelp: 'sysadmin_certs_acme_plugins',
+
+    isAdd: true,
+    isCreate: false,
+
+    width: 550,
+
+    acmeUrl: undefined,
+
+    subject: 'ACME DNS Plugin',
+
+    cbindData: function(config) {
+	let me = this;
+	return {
+	    challengeSchemaUrl: `/api2/json/${me.acmeUrl}/challenge-schema`,
+	};
+    },
+
+    items: [
+	{
+	    xtype: 'inputpanel',
+	    // we dynamically create fields from the given schema
+	    // things we have to do here:
+	    // * save which fields we created to remove them again
+	    // * split the data from the generic 'data' field into the boxes
+	    // * on deletion collect those values again
+	    // * save the original values of the data field
+	    createdFields: {},
+	    createdInitially: false,
+	    originalValues: {},
+	    createSchemaFields: function(schema) {
+		let me = this;
+		// we know where to add because we define it right below
+		let container = me.down('container');
+		let datafield = me.down('field[name=data]');
+		let hintfield = me.down('field[name=hint]');
+		if (!me.createdInitially) {
+		    [me.originalValues] = Proxmox.Utils.parseACMEPluginData(datafield.getValue());
+		}
+
+		// collect values from custom fields and add it to 'data'',
+		// then remove the custom fields
+		let data = [];
+		for (const [name, field] of Object.entries(me.createdFields)) {
+		    let value = field.getValue();
+		    if (value !== undefined && value !== null && value !== '') {
+			data.push(`${name}=${value}`);
+		    }
+		    container.remove(field);
+		}
+		let datavalue = datafield.getValue();
+		if (datavalue !== undefined && datavalue !== null && datavalue !== '') {
+		    data.push(datavalue);
+		}
+		datafield.setValue(data.join('\n'));
+
+		me.createdFields = {};
+
+		if (typeof schema.fields !== 'object') {
+		    schema.fields = {};
+		}
+		// create custom fields according to schema
+		let gotSchemaField = false;
+		for (const [name, definition] of Object
+		    .entries(schema.fields)
+		    .sort((a, b) => a[0].localeCompare(b[0]))
+		) {
+		    let xtype;
+		    switch (definition.type) {
+			case 'string':
+			    xtype = 'proxmoxtextfield';
+			    break;
+			case 'integer':
+			    xtype = 'proxmoxintegerfield';
+			    break;
+			case 'number':
+			    xtype = 'numberfield';
+			    break;
+			default:
+			    console.warn(`unknown type '${definition.type}'`);
+			    xtype = 'proxmoxtextfield';
+			    break;
+		    }
+
+		    let label = name;
+		    if (typeof definition.name === "string") {
+			label = definition.name;
+		    }
+
+		    let field = Ext.create({
+			xtype,
+			name: `custom_${name}`,
+			fieldLabel: label,
+			width: '100%',
+			labelWidth: 150,
+			labelSeparator: '=',
+			emptyText: definition.default || '',
+			autoEl: definition.description ? {
+			    tag: 'div',
+			    'data-qtip': definition.description,
+			} : undefined,
+		    });
+
+		    me.createdFields[name] = field;
+		    container.add(field);
+		    gotSchemaField = true;
+		}
+		datafield.setHidden(gotSchemaField); // prefer schema-fields
+
+		if (schema.description) {
+		    hintfield.setValue(schema.description);
+		    hintfield.setHidden(false);
+		} else {
+		    hintfield.setValue('');
+		    hintfield.setHidden(true);
+		}
+
+		// parse data from field and set it to the custom ones
+		let extradata = [];
+		[data, extradata] = Proxmox.Utils.parseACMEPluginData(datafield.getValue());
+		for (const [key, value] of Object.entries(data)) {
+		    if (me.createdFields[key]) {
+			me.createdFields[key].setValue(value);
+			me.createdFields[key].originalValue = me.originalValues[key];
+		    } else {
+			extradata.push(`${key}=${value}`);
+		    }
+		}
+		datafield.setValue(extradata.join('\n'));
+		if (!me.createdInitially) {
+		    datafield.resetOriginalValue();
+		    me.createdInitially = true; // save that we initally set that
+		}
+	    },
+
+	    onGetValues: function(values) {
+		let me = this;
+		let win = me.up('pmxACMEPluginEdit');
+		if (win.isCreate) {
+		    values.id = values.plugin;
+		    values.type = 'dns'; // the only one for now
+		}
+		delete values.plugin;
+
+		Proxmox.Utils.delete_if_default(values, 'validation-delay', '30', win.isCreate);
+
+		let data = '';
+		for (const [name, field] of Object.entries(me.createdFields)) {
+		    let value = field.getValue();
+		    if (value !== null && value !== undefined && value !== '') {
+			data += `${name}=${value}\n`;
+		    }
+		    delete values[`custom_${name}`];
+		}
+		values.data = Ext.util.Base64.encode(data + values.data);
+		return values;
+	    },
+
+	    items: [
+		{
+		    xtype: 'pmxDisplayEditField',
+		    cbind: {
+			editable: (get) => get('isCreate'),
+			submitValue: (get) => get('isCreate'),
+		    },
+		    editConfig: {
+			flex: 1,
+			xtype: 'proxmoxtextfield',
+			allowBlank: false,
+		    },
+		    name: 'plugin',
+		    labelWidth: 150,
+		    fieldLabel: gettext('Plugin ID'),
+		},
+		{
+		    xtype: 'proxmoxintegerfield',
+		    name: 'validation-delay',
+		    labelWidth: 150,
+		    fieldLabel: gettext('Validation Delay'),
+		    emptyText: 30,
+		    cbind: {
+			deleteEmpty: '{!isCreate}',
+		    },
+		    minValue: 0,
+		    maxValue: 48*60*60,
+		},
+		{
+		    xtype: 'pmxACMEApiSelector',
+		    name: 'api',
+		    labelWidth: 150,
+		    cbind: {
+			url: '{challengeSchemaUrl}',
+		    },
+		    listeners: {
+			change: function(selector) {
+			    let schema = selector.getSchema();
+			    selector.up('inputpanel').createSchemaFields(schema);
+			},
+		    },
+		},
+		{
+		    xtype: 'textarea',
+		    fieldLabel: gettext('API Data'),
+		    labelWidth: 150,
+		    name: 'data',
+		},
+		{
+		    xtype: 'displayfield',
+		    fieldLabel: gettext('Hint'),
+		    labelWidth: 150,
+		    name: 'hint',
+		    hidden: true,
+		},
+	    ],
+	},
+    ],
+
+    initComponent: function() {
+	var me = this;
+
+	if (!me.acmeUrl) {
+	    throw "no acmeUrl given";
+	}
+
+	me.callParent();
+
+	if (!me.isCreate) {
+	    me.load({
+		success: function(response, opts) {
+		    me.setValues(response.result.data);
+		},
+	    });
+	} else {
+	    me.method = 'POST';
+	}
+    },
+});
-- 
2.20.1





More information about the pmg-devel mailing list