[pve-devel] [PATCH manager 1/2] add node/ACME.js

Thomas Lamprecht t.lamprecht at proxmox.com
Mon Apr 30 14:25:31 CEST 2018


On 4/25/18 11:41 AM, Dominik Csapak wrote:
> this provides the grid for editing domains for letsencrypt,
> order/renew the certificates, and the window for creating an
> ACME account
> 
> Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
> ---
>  www/manager6/Makefile     |   1 +
>  www/manager6/Parser.js    |  29 ++++
>  www/manager6/node/ACME.js | 328 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 358 insertions(+)
>  create mode 100644 www/manager6/node/ACME.js
> 
> diff --git a/www/manager6/Makefile b/www/manager6/Makefile
> index 60e8103e..c29824bf 100644
> --- a/www/manager6/Makefile
> +++ b/www/manager6/Makefile
> @@ -97,6 +97,7 @@ JSSRC= 				                 	\
>  	node/StatusView.js				\
>  	node/Summary.js					\
>  	node/Subscription.js				\
> +	node/ACME.js					\
>  	node/Config.js					\
>  	window/Migrate.js				\
>  	window/BulkAction.js				\
> diff --git a/www/manager6/Parser.js b/www/manager6/Parser.js
> index 8253bd80..13dce766 100644
> --- a/www/manager6/Parser.js
> +++ b/www/manager6/Parser.js
> @@ -5,6 +5,35 @@ Ext.define('PVE.Parser', { statics: {
>  
>      // this class only contains static functions
>  
> +    parseACME: function(value) {
> +	if (!value) {
> +	    return;
> +	}
> +
> +	var res = {};
> +	var errors = false;
> +
> +	Ext.Array.each(value.split(','), function(p) {
> +	    if (!p || p.match(/^\s*$/)) {
> +		return; //continue
> +	    }
> +
> +	    var match_res;
> +	    if ((match_res = p.match(/^(?:domains=)?((?:[a-zA-Z0-9\-\.]+[;, ]?)+)$/)) !== null) {
> +		res.domains = match_res[1].split(/[;, ]/);
> +	    } else {
> +		errors = true;
> +		return false;
> +	    }
> +	});
> +
> +	if (errors || !res) {
> +	    return;
> +	}
> +
> +	return res;
> +    },
> +
>      parseBoolean: function(value, default_value) {
>  	if (!Ext.isDefined(value)) {
>  	    return default_value;
> diff --git a/www/manager6/node/ACME.js b/www/manager6/node/ACME.js
> new file mode 100644
> index 00000000..fc6fbd50
> --- /dev/null
> +++ b/www/manager6/node/ACME.js
> @@ -0,0 +1,328 @@
> +Ext.define('PVE.node.ACMEEditor', {
> +    extend: 'Proxmox.window.Edit',
> +    xtype: 'pveACMEEditor',
> +
> +    subject: gettext('Domains'),
> +    items: [
> +	{
> +	    xtype: 'inputpanel',
> +	    items: [
> +		{
> +		    xtype: 'textfield',

I'd make this a text area and give the user information
that he can add multiple domains through newline separation,
either as emptyText or as tooltip.

> +		    fieldLabel: gettext('Domains'),
> +		    emptyText: Proxmox.Utils.noneText,
> +		    name: 'domains'
> +		}
> +	    ],
> +	    onGetValues: function(values) {
> +		if (!values.domains) {
> +		    return {
> +			'delete': 'acme'
> +		    };
> +		}
> +		var domains = values.domains.split(/[,; ]/).join(';');
> +		return {
> +		    'acme': 'domains=' + domains
> +		};
> +	    }
> +	}
> +    ],
> +
> +    initComponent: function() {
> +	var me = this;
> +	me.callParent();
> +
> +	me.load({
> +	    success: function(response, opts) {
> +		var res = PVE.Parser.parseACME(response.result.data.acme);
> +		if (res) {
> +		    res.domains = res.domains.join(' ');
> +		    me.setValues(res);
> +		}
> +	    }
> +	});
> +    }
> +});
> +
> +Ext.define('PVE.node.ACMEAccountCreate', {
> +    extend: 'Proxmox.window.Edit',
> +
> +    width: 400,
> +    title: gettext('Register Account'),
> +    isCreate: true,
> +    method: 'POST',
> +    submitText: gettext('Register'),
> +    url: '/cluster/acme/account',
> +    showTaskViewer: true,
> +
> +    items: [
> +	{
> +	    xtype: 'proxmoxComboGrid',
> +	    name: 'directory',
> +	    allowBlank: false,
> +	    valueField: 'url',
> +	    displayField: 'name',
> +	    fieldLabel: gettext('ACME Directory'),
> +	    store: {
> +		autoLoad: true,
> +		fields: ['name', 'url'],
> +		idProperty: ['name'],
> +		proxy: {
> +		    type: 'proxmox',
> +		    url: '/api2/json/cluster/acme/directories'
> +		},
> +		sorters: {
> +		    property: 'name',
> +		    order: 'ASC'
> +		}
> +	    },
> +	    listConfig: {
> +		columns: [
> +		    {
> +			header: gettext('Name'),
> +			dataIndex: 'name',
> +			flex: 1
> +		    },
> +		    {
> +			header: gettext('URL'),
> +			dataIndex: 'url',
> +			flex: 1
> +		    }
> +		]
> +	    },
> +	    listeners: {
> +		change: function(combogrid, value) {
> +		    var me = this;
> +		    if (!value) {
> +			return;
> +		    }
> +
> +		    var disp = me.up('window').down('#tos_url_display');
> +		    var field = me.up('window').down('#tos_url');
> +		    var checkbox = me.up('window').down('#tos_checkbox');
> +
> +		    disp.setValue(gettext('Loading'));
> +		    field.setValue(undefined);
> +		    checkbox.setValue(undefined);
> +
> +		    Proxmox.Utils.API2Request({
> +			url: '/cluster/acme/tos',
> +			method: 'GET',
> +			params: {
> +			    directory: value
> +			},
> +			success: function(response, opt) {
> +			    me.up('window').down('#tos_url').setValue(response.result.data);
> +			    me.up('window').down('#tos_url_display').setValue(response.result.data);
> +			},
> +			failure: function(response, opt) {
> +			    Ext.Msg.alert(gettext('Error'), response.htmlStatus);
> +			}
> +		    });
> +		}
> +	    }
> +	},
> +	{
> +	    xtype: 'displayfield',
> +	    itemId: 'tos_url_display',
> +	    fieldLabel: gettext('Terms of Service'),
> +	    name: 'tos_url_display'
> +	},

I'd add a fa-external-link icon add the end, either if https?://
is detected or always (as currently we only get url here (as your
field name suggests ;) ))


> +	{
> +	    xtype: 'hidden',
> +	    itemId: 'tos_url',
> +	    name: 'tos_url'
> +	},
> +	{
> +	    xtype: 'proxmoxcheckbox',
> +	    itemId: 'tos_checkbox',
> +	    fieldLabel: gettext('Accept TOS'),
> +	    submitValue: false,
> +	    validateValue: function(value) {
> +		if (value && this.checked) {
> +		    return true;
> +		}
> +		return false;
> +	    }
> +	},
> +	{
> +	    xtype: 'textfield',
> +	    name: 'contact',
> +	    vtype: 'email',
> +	    allowBlank: false,
> +	    fieldLabel: gettext('E-Mail')
> +	}
> +    ]
> +
> +});
> +
> +Ext.define('PVE.node.ACME', {
> +    extend: 'Proxmox.grid.ObjectGrid',
> +    xtype: 'pveACMEView',
> +
> +    margin: '10 0 0 0',
> +    title: 'ACME',
> +
> +    tbar: [
> +	{
> +	    xtype: 'button',
> +	    itemId: 'edit',
> +	    text: gettext('Edit'),
> +	    handler: function() {
> +		this.up('grid').run_editor();
> +	    }

Edit what?
Here, currently, it's not quite clear what will get edited if
I just opened this panel, as no row must be selected, currently
there's only the domain field to edit so maybe change it to:
'Edit Domains'.

> +	},
> +	{
> +	    xtype: 'button',
> +	    itemId: 'createaccount',
> +	    text: gettext('Register Account'),
> +	    handler: function() {
> +		var me = this.up('grid');
> +		var win = Ext.create('PVE.node.ACMEAccountCreate', {
> +		    taskDone: function() {
> +			me.reload();
> +		    }
> +		});
> +		win.show();
> +	    }

I would give some (minimal) feedback about an created account.

> +	},
> +	{
> +	    xtype: 'button',
> +	    itemId: 'order',
> +	    text: gettext('Order Certificate'),
> +	    handler: function() {
> +		var me = this.up('grid');
> +
> +		Proxmox.Utils.API2Request({
> +		    method: 'POST',
> +		    params: {
> +			force: 1
> +		    },
> +		    url: '/nodes/' + me.nodename + '/certificates/acme/certificate',
> +		    success: function(response, opt) {
> +			var win = Ext.create('Proxmox.window.TaskViewer', {
> +			    upid: response.result.data
> +			});
> +			win.show();
> +		    },
> +		    failure: function(response, opt) {
> +			Ext.Msg.alert(gettext('Error'), response.htmlStatus);
> +		    }
> +		});
> +	    }
> +	},
> +	{
> +	    xtype: 'button',
> +	    itemId: 'renew',
> +	    text: gettext('Renew Certificate'),
> +	    handler: function() {
> +		var me = this.up('grid');
> +		console.log('test');
> +		Proxmox.Utils.API2Request({
> +		    method: 'PUT',
> +		    params: {
> +			force: 1
> +		    },
> +		    url: '/nodes/' + me.nodename + '/certificates/acme/certificate',
> +		    success: function(response, opt) {
> +			console.log('tes2');
> +			var win = Ext.create('Proxmox.window.TaskViewer', {
> +			    upid: response.result.data
> +			});
> +			win.show();
> +		    },
> +		    failure: function(response, opt) {
> +			Ext.Msg.alert(gettext('Error'), response.htmlStatus);
> +		    }
> +		});
> +	    }
> +	}
> +    ],
> +
> +    set_button_status: function() {
> +	var me = this;
> +
> +	var account = !!me.account;
> +	var acmeObj = PVE.Parser.parseACME(me.getObjectValue('acme'));
> +	var domains = acmeObj ? acmeObj.domains.length : 0;
> +
> +	var order = me.down('#order');
> +	var renew = me.down('#renew');
> +	order.setVisible(account);
> +	order.setDisabled(!account || !domains);
> +	renew.setVisible(account);
> +	renew.setDisabled(!account || !domains);
> +
> +
> +	me.down('#createaccount').setVisible(!account);
> +    },
> +
> +    load_account: function() {
> +	var me = this;
> +
> +	// for now we only use the 'default' account
> +	Proxmox.Utils.API2Request({
> +	    url: '/cluster/acme/account/default',
> +	    success: function(response, opt) {
> +		me.account = response.result.data;
> +		me.set_button_status();
> +	    },
> +	    failure: function(response, opt) {
> +		me.account = undefined;
> +		me.set_button_status();
> +	    }
> +	});
> +    },
> +
> +    run_editor: function() {
> +	var me = this;
> +	var win = Ext.create(me.rows.acme.editor, me.editorConfig);
> +	win.show();
> +	win.on('destroy', me.reload, me);
> +    },
> +
> +    listeners: {
> +	itemdblclick: 'run_editor'
> +    },
> +
> +    // account data gets loaded here
> +    account: undefined,
> +
> +    disableSelection: true,
> +
> +    initComponent: function() {
> +	var me = this;
> +
> +	if (!me.nodename) {
> +	    throw "no nodename given";
> +	}
> +
> +	me.url = '/api2/json/nodes/' + me.nodename + '/config';
> +
> +	me.editorConfig = {
> +	    url: '/api2/extjs/nodes/' + me.nodename + '/config'
> +	};
> +	/*jslint confusion: true*/
> +	/*acme is a string above*/
> +	me.rows = {
> +	    acme: {
> +		defaultValue: '',
> +		header: gettext('Domains'),
> +		editor: 'PVE.node.ACMEEditor',
> +		renderer: function(value) {
> +		    var acmeObj = PVE.Parser.parseACME(value);
> +		    if (acmeObj) {
> +			return acmeObj.domains.join('<br>');
> +		    }
> +		    return Proxmox.Utils.noneText;
> +		}
> +	    }
> +	};
> +	/*jslint confusion: false*/
> +
> +	me.callParent();
> +	me.mon(me.rstore, 'load', me.set_button_status, me);
> +	me.rstore.startUpdate();
> +	me.load_account();
> +    }
> +});
> 




More information about the pve-devel mailing list