[pve-devel] [PATCH manager v2 2/3] add node/ACME.js
Dominik Csapak
d.csapak at proxmox.com
Fri May 4 11:53:34 CEST 2018
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 | 427 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 457 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..febb16e1
--- /dev/null
+++ b/www/manager6/node/ACME.js
@@ -0,0 +1,427 @@
+Ext.define('PVE.node.ACMEEditor', {
+ extend: 'Proxmox.window.Edit',
+ xtype: 'pveACMEEditor',
+
+ subject: gettext('Domains'),
+ items: [
+ {
+ xtype: 'inputpanel',
+ items: [
+ {
+ xtype: 'textarea',
+ fieldLabel: gettext('Domains'),
+ emptyText: "domain1.example.com\ndomain2.example.com",
+ name: 'domains'
+ }
+ ],
+ onGetValues: function(values) {
+ if (!values.domains) {
+ return {
+ 'delete': 'acme'
+ };
+ }
+ var domains = values.domains.split(/\n/).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'),
+ renderer: PVE.Utils.render_optional_url,
+ name: 'tos_url_display'
+ },
+ {
+ 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.ACMEAccountView', {
+ extend: 'Proxmox.window.Edit',
+
+ width: 600,
+ fieldDefaults: {
+ labelWidth: 140
+ },
+
+ title: gettext('Account'),
+
+ items: [
+ {
+ xtype: 'displayfield',
+ fieldLabel: gettext('E-Mail'),
+ name: 'email'
+ },
+ {
+ xtype: 'displayfield',
+ fieldLabel: gettext('Created'),
+ name: 'createdAt'
+ },
+ {
+ xtype: 'displayfield',
+ fieldLabel: gettext('Status'),
+ name: 'status'
+ },
+ {
+ xtype: 'displayfield',
+ fieldLabel: gettext('Directory'),
+ renderer: PVE.Utils.render_optional_url,
+ name: 'directory'
+ },
+ {
+ xtype: 'displayfield',
+ fieldLabel: gettext('Terms of Services'),
+ renderer: PVE.Utils.render_optional_url,
+ name: 'tos'
+ }
+ ],
+
+ initComponent: function() {
+ var me = this;
+
+ if (!me.accountname) {
+ throw "no account name defined";
+ }
+
+ me.url = '/cluster/acme/account/' + me.accountname;
+
+ me.callParent();
+
+ // hide OK/Reset button, because we just want to show data
+ me.down('toolbar[dock=bottom]').setVisible(false);
+
+ me.load({
+ success: function(response) {
+ var data = response.result.data;
+ data.email = data.account.contact[0];
+ data.createdAt = data.account.createdAt;
+ data.status = data.account.status;
+ me.setValues(data);
+ }
+ });
+ }
+});
+
+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 Domains'),
+ handler: function() {
+ this.up('grid').run_editor();
+ }
+ },
+ {
+ 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.load_account();
+ me.reload();
+ }
+ });
+ win.show();
+ }
+ },
+ {
+ xtype: 'button',
+ itemId: 'viewaccount',
+ text: gettext('View Account'),
+ handler: function() {
+ var me = this.up('grid');
+ var win = Ext.create('PVE.node.ACMEAccountView', {
+ accountname: 'default'
+ });
+ win.show();
+ }
+ },
+ {
+ 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,
+ taskDone: function(success) {
+ me.certificate_order_finished(success);
+ }
+ });
+ win.show();
+ },
+ failure: function(response, opt) {
+ Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+ }
+ });
+ }
+ },
+ {
+ xtype: 'proxmoxButton',
+ itemId: 'renew',
+ text: gettext('Renew Certificate'),
+ selModel: null,
+ confirmMsg: gettext("Are you sure you want to force renew the certificate?"),
+ handler: function() {
+ var me = this.up('grid');
+ Proxmox.Utils.API2Request({
+ method: 'PUT',
+ 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,
+ taskDone: function(success) {
+ me.certificate_order_finished(success);
+ }
+ });
+ win.show();
+ },
+ failure: function(response, opt) {
+ Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+ }
+ });
+ }
+ }
+ ],
+
+ certificate_order_finished: function(success) {
+ if (!success) {
+ return;
+ }
+ var txt = gettext('pveproxy will be restarted with new certificates, please reload the GUI!');
+ Ext.getBody().mask(txt, ['pve-static-mask']);
+ // reload after 10 seconds automatically
+ Ext.defer(function() {
+ window.location.reload(true);
+ }, 10000);
+ },
+
+ 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);
+ me.down('#viewaccount').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();
+ }
+});
--
2.11.0
More information about the pve-devel
mailing list