[pmg-devel] [PATCH v3 widget-toolkit 6/7] add ACME plugin editing
Wolfgang Bumiller
w.bumiller at proxmox.com
Tue Mar 16 11:24:22 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 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
More information about the pmg-devel
mailing list