public inbox for pmg-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Wolfgang Bumiller <w.bumiller@proxmox.com>
To: pmg-devel@lists.proxmox.com
Subject: [pmg-devel] [PATCH v3 widget-toolkit 6/7] add ACME plugin editing
Date: Tue, 16 Mar 2021 11:24:22 +0100	[thread overview]
Message-ID: <20210316102424.25885-16-w.bumiller@proxmox.com> (raw)
In-Reply-To: <20210316102424.25885-1-w.bumiller@proxmox.com>

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@proxmox.com>
---
No changes since v2

 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





  parent reply	other threads:[~2021-03-16 10:24 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-03-16 10:24 [pmg-devel] [PATCH v3 api/gui/wtk/acme 0/many] Certificates & ACME Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 api 1/8] depend on libpmg-rs-perl and proxmox-acme Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 api 2/8] add PMG::CertHelpers module Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 api 3/8] add PMG::NodeConfig module Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 api 4/8] cluster: sync acme/ and acme-plugins.conf Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 api 5/8] api: add ACME and ACMEPlugin module Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 api 6/8] add certificates api endpoint Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 api 7/8] add node-config api entry points Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 api 8/8] add acme and cert subcommands to pmgconfig Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 gui] add certificates and acme view Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 widget-toolkit 1/7] Utils: add ACME related utilities Wolfgang Bumiller
2021-03-16 12:18   ` [pmg-devel] applied-series[wtk]: " Thomas Lamprecht
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 widget-toolkit 2/7] add ACME related data models Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 widget-toolkit 3/7] add ACME forms Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 widget-toolkit 4/7] add certificate panel Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 widget-toolkit 5/7] add ACME account panel Wolfgang Bumiller
2021-03-16 10:24 ` Wolfgang Bumiller [this message]
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 widget-toolkit 7/7] add ACME domain editing Wolfgang Bumiller
2021-03-16 10:24 ` [pmg-devel] [PATCH v3 common] get_options: don't set optional positional params to `undef` Wolfgang Bumiller
2021-03-16 12:17   ` [pmg-devel] applied: " Thomas Lamprecht
2021-03-16 17:04 ` [pmg-devel] applied-series: [PATCH v3 api/gui/wtk/acme 0/many] Certificates & ACME Thomas Lamprecht

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=20210316102424.25885-16-w.bumiller@proxmox.com \
    --to=w.bumiller@proxmox.com \
    --cc=pmg-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
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal