mirror of
				https://git.proxmox.com/git/proxmox-widget-toolkit
				synced 2025-11-04 08:17:54 +00:00 
			
		
		
		
	add ACME domain editing
Same deal, however, here the PVE code is has a little bug where changing the plugin type of a domain makes it disappear, so this also contains some fixups. Additionally, this now also adds the ability to change a domain's "usage" (smtp, api or both), so similar to the uploadButtons info in the Certificates panel, we now have a domainUsages info. If it is set, the edit window will show a multiselect combobox, and the panel will show a usage column. Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
		
							parent
							
								
									658bfdff32
								
							
						
					
					
						commit
						8915422f90
					
				@ -52,6 +52,7 @@ JSSRC=					\
 | 
			
		||||
	panel/Certificates.js		\
 | 
			
		||||
	panel/ACMEAccount.js		\
 | 
			
		||||
	panel/ACMEPlugin.js		\
 | 
			
		||||
	panel/ACMEDomains.js		\
 | 
			
		||||
	window/Edit.js			\
 | 
			
		||||
	window/PasswordEdit.js		\
 | 
			
		||||
	window/SafeDestroy.js		\
 | 
			
		||||
@ -62,6 +63,7 @@ JSSRC=					\
 | 
			
		||||
	window/Certificates.js		\
 | 
			
		||||
	window/ACMEAccount.js		\
 | 
			
		||||
	window/ACMEPluginEdit.js	\
 | 
			
		||||
	window/ACMEDomains.js		\
 | 
			
		||||
	node/APT.js			\
 | 
			
		||||
	node/NetworkEdit.js		\
 | 
			
		||||
	node/NetworkView.js		\
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										492
									
								
								src/panel/ACMEDomains.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										492
									
								
								src/panel/ACMEDomains.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,492 @@
 | 
			
		||||
Ext.define('proxmox-acme-domains', {
 | 
			
		||||
    extend: 'Ext.data.Model',
 | 
			
		||||
    fields: ['domain', 'type', 'alias', 'plugin', 'configkey'],
 | 
			
		||||
    idProperty: 'domain',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Ext.define('Proxmox.panel.ACMEDomains', {
 | 
			
		||||
    extend: 'Ext.grid.Panel',
 | 
			
		||||
    xtype: 'pmxACMEDomains',
 | 
			
		||||
    mixins: ['Proxmox.Mixin.CBind'],
 | 
			
		||||
 | 
			
		||||
    margin: '10 0 0 0',
 | 
			
		||||
    title: 'ACME',
 | 
			
		||||
 | 
			
		||||
    emptyText: gettext('No Domains configured'),
 | 
			
		||||
 | 
			
		||||
    // URL to the config containing 'acme' and 'acmedomainX' properties
 | 
			
		||||
    url: undefined,
 | 
			
		||||
 | 
			
		||||
    // array of { name, url, usageLabel }
 | 
			
		||||
    domainUsages: undefined,
 | 
			
		||||
    // if no domainUsages parameter is supllied, the orderUrl is required instead:
 | 
			
		||||
    orderUrl: undefined,
 | 
			
		||||
 | 
			
		||||
    acmeUrl: undefined,
 | 
			
		||||
 | 
			
		||||
    cbindData: function(config) {
 | 
			
		||||
	let me = this;
 | 
			
		||||
	return {
 | 
			
		||||
	    acmeUrl: me.acmeUrl,
 | 
			
		||||
	    accountUrl: `/api2/json/${me.acmeUrl}/account`,
 | 
			
		||||
	};
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    viewModel: {
 | 
			
		||||
	data: {
 | 
			
		||||
	    domaincount: 0,
 | 
			
		||||
	    account: undefined, // the account we display
 | 
			
		||||
	    configaccount: undefined, // the account set in the config
 | 
			
		||||
	    accountEditable: false,
 | 
			
		||||
	    accountsAvailable: false,
 | 
			
		||||
	    hasUsage: false,
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	formulas: {
 | 
			
		||||
	    canOrder: (get) => !!get('account') && get('domaincount') > 0,
 | 
			
		||||
	    editBtnIcon: (get) => 'fa black fa-' + (get('accountEditable') ? 'check' : 'pencil'),
 | 
			
		||||
	    accountTextHidden: (get) => get('accountEditable') || !get('accountsAvailable'),
 | 
			
		||||
	    accountValueHidden: (get) => !get('accountEditable') || !get('accountsAvailable'),
 | 
			
		||||
	    hasUsage: (get) => get('hasUsage'),
 | 
			
		||||
	},
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    controller: {
 | 
			
		||||
	xclass: 'Ext.app.ViewController',
 | 
			
		||||
 | 
			
		||||
	init: function(view) {
 | 
			
		||||
	    let accountSelector = this.lookup('accountselector');
 | 
			
		||||
	    accountSelector.store.on('load', this.onAccountsLoad, this);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	onAccountsLoad: function(store, records, success) {
 | 
			
		||||
	    let me = this;
 | 
			
		||||
	    let vm = me.getViewModel();
 | 
			
		||||
	    let configaccount = vm.get('configaccount');
 | 
			
		||||
	    vm.set('accountsAvailable', records.length > 0);
 | 
			
		||||
	    if (me.autoChangeAccount && records.length > 0) {
 | 
			
		||||
		me.changeAccount(records[0].data.name, () => {
 | 
			
		||||
		    vm.set('accountEditable', false);
 | 
			
		||||
		    me.reload();
 | 
			
		||||
		});
 | 
			
		||||
		me.autoChangeAccount = false;
 | 
			
		||||
	    } else if (configaccount) {
 | 
			
		||||
		if (store.findExact('name', configaccount) !== -1) {
 | 
			
		||||
		    vm.set('account', configaccount);
 | 
			
		||||
		} else {
 | 
			
		||||
		    vm.set('account', null);
 | 
			
		||||
		}
 | 
			
		||||
	    }
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	addDomain: function() {
 | 
			
		||||
	    let me = this;
 | 
			
		||||
	    let view = me.getView();
 | 
			
		||||
 | 
			
		||||
	    Ext.create('Proxmox.window.ACMEDomainEdit', {
 | 
			
		||||
		url: view.url,
 | 
			
		||||
		acmeUrl: view.acmeUrl,
 | 
			
		||||
		nodeconfig: view.nodeconfig,
 | 
			
		||||
		domainUsages: view.domainUsages,
 | 
			
		||||
		apiCallDone: function() {
 | 
			
		||||
		    me.reload();
 | 
			
		||||
		},
 | 
			
		||||
	    }).show();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	editDomain: function() {
 | 
			
		||||
	    let me = this;
 | 
			
		||||
	    let view = me.getView();
 | 
			
		||||
 | 
			
		||||
	    let selection = view.getSelection();
 | 
			
		||||
	    if (selection.length < 1) return;
 | 
			
		||||
 | 
			
		||||
	    Ext.create('Proxmox.window.ACMEDomainEdit', {
 | 
			
		||||
		url: view.url,
 | 
			
		||||
		acmeUrl: view.acmeUrl,
 | 
			
		||||
		nodeconfig: view.nodeconfig,
 | 
			
		||||
		domainUsages: view.domainUsages,
 | 
			
		||||
		domain: selection[0].data,
 | 
			
		||||
		apiCallDone: function() {
 | 
			
		||||
		    me.reload();
 | 
			
		||||
		},
 | 
			
		||||
	    }).show();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	removeDomain: function() {
 | 
			
		||||
	    let me = this;
 | 
			
		||||
	    let view = me.getView();
 | 
			
		||||
	    let selection = view.getSelection();
 | 
			
		||||
	    if (selection.length < 1) return;
 | 
			
		||||
 | 
			
		||||
	    let rec = selection[0].data;
 | 
			
		||||
	    let params = {};
 | 
			
		||||
	    if (rec.configkey !== 'acme') {
 | 
			
		||||
		params.delete = rec.configkey;
 | 
			
		||||
	    } else {
 | 
			
		||||
		let acme = Proxmox.Utils.parseACME(view.nodeconfig.acme);
 | 
			
		||||
		Proxmox.Utils.remove_domain_from_acme(acme, rec.domain);
 | 
			
		||||
		params.acme = Proxmox.Utils.printACME(acme);
 | 
			
		||||
	    }
 | 
			
		||||
 | 
			
		||||
	    Proxmox.Utils.API2Request({
 | 
			
		||||
		method: 'PUT',
 | 
			
		||||
		url: view.url,
 | 
			
		||||
		params,
 | 
			
		||||
		success: function(response, opt) {
 | 
			
		||||
		    me.reload();
 | 
			
		||||
		},
 | 
			
		||||
		failure: function(response, opt) {
 | 
			
		||||
		    Ext.Msg.alert(gettext('Error'), response.htmlStatus);
 | 
			
		||||
		},
 | 
			
		||||
	    });
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	toggleEditAccount: function() {
 | 
			
		||||
	    let me = this;
 | 
			
		||||
	    let vm = me.getViewModel();
 | 
			
		||||
	    let editable = vm.get('accountEditable');
 | 
			
		||||
	    if (editable) {
 | 
			
		||||
		me.changeAccount(vm.get('account'), function() {
 | 
			
		||||
		    vm.set('accountEditable', false);
 | 
			
		||||
		    me.reload();
 | 
			
		||||
		});
 | 
			
		||||
	    } else {
 | 
			
		||||
		vm.set('accountEditable', true);
 | 
			
		||||
	    }
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	changeAccount: function(account, callback) {
 | 
			
		||||
	    let me = this;
 | 
			
		||||
	    let view = me.getView();
 | 
			
		||||
	    let params = {};
 | 
			
		||||
 | 
			
		||||
	    let acme = Proxmox.Utils.parseACME(view.nodeconfig.acme);
 | 
			
		||||
	    acme.account = account;
 | 
			
		||||
	    params.acme = Proxmox.Utils.printACME(acme);
 | 
			
		||||
 | 
			
		||||
	    Proxmox.Utils.API2Request({
 | 
			
		||||
		method: 'PUT',
 | 
			
		||||
		waitMsgTarget: view,
 | 
			
		||||
		url: view.url,
 | 
			
		||||
		params,
 | 
			
		||||
		success: function(response, opt) {
 | 
			
		||||
		    if (Ext.isFunction(callback)) {
 | 
			
		||||
			callback();
 | 
			
		||||
		    }
 | 
			
		||||
		},
 | 
			
		||||
		failure: function(response, opt) {
 | 
			
		||||
		    Ext.Msg.alert(gettext('Error'), response.htmlStatus);
 | 
			
		||||
		},
 | 
			
		||||
	    });
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	order: function(cert) {
 | 
			
		||||
	    let me = this;
 | 
			
		||||
	    let view = me.getView();
 | 
			
		||||
 | 
			
		||||
	    Proxmox.Utils.API2Request({
 | 
			
		||||
		method: 'POST',
 | 
			
		||||
		params: {
 | 
			
		||||
		    force: 1,
 | 
			
		||||
		},
 | 
			
		||||
		url: cert ? cert.url : view.orderUrl,
 | 
			
		||||
		success: function(response, opt) {
 | 
			
		||||
		    Ext.create('Proxmox.window.TaskViewer', {
 | 
			
		||||
		        upid: response.result.data,
 | 
			
		||||
		        taskDone: function(success) {
 | 
			
		||||
			    me.orderFinished(success, cert);
 | 
			
		||||
		        },
 | 
			
		||||
		    }).show();
 | 
			
		||||
		},
 | 
			
		||||
		failure: function(response, opt) {
 | 
			
		||||
		    Ext.Msg.alert(gettext('Error'), response.htmlStatus);
 | 
			
		||||
		},
 | 
			
		||||
	    });
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	orderFinished: function(success, cert) {
 | 
			
		||||
	    if (!success || !cert.reloadUi) return;
 | 
			
		||||
	    var txt = gettext('gui will be restarted with new certificates, please reload!');
 | 
			
		||||
	    Ext.getBody().mask(txt, ['x-mask-loading']);
 | 
			
		||||
	    // reload after 10 seconds automatically
 | 
			
		||||
	    Ext.defer(function() {
 | 
			
		||||
		window.location.reload(true);
 | 
			
		||||
	    }, 10000);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	reload: function() {
 | 
			
		||||
	    let me = this;
 | 
			
		||||
	    let view = me.getView();
 | 
			
		||||
	    view.rstore.load();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	addAccount: function() {
 | 
			
		||||
	    let me = this;
 | 
			
		||||
	    let view = me.getView();
 | 
			
		||||
	    Ext.create('Proxmox.window.ACMEAccountCreate', {
 | 
			
		||||
		autoShow: true,
 | 
			
		||||
		acmeUrl: view.acmeUrl,
 | 
			
		||||
		taskDone: function() {
 | 
			
		||||
		    me.reload();
 | 
			
		||||
		    let accountSelector = me.lookup('accountselector');
 | 
			
		||||
		    me.autoChangeAccount = true;
 | 
			
		||||
		    accountSelector.store.load();
 | 
			
		||||
		},
 | 
			
		||||
	    });
 | 
			
		||||
	},
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    tbar: [
 | 
			
		||||
	{
 | 
			
		||||
	    xtype: 'proxmoxButton',
 | 
			
		||||
	    text: gettext('Add'),
 | 
			
		||||
	    handler: 'addDomain',
 | 
			
		||||
	    selModel: false,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
	    xtype: 'proxmoxButton',
 | 
			
		||||
	    text: gettext('Edit'),
 | 
			
		||||
	    disabled: true,
 | 
			
		||||
	    handler: 'editDomain',
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
	    xtype: 'proxmoxStdRemoveButton',
 | 
			
		||||
	    handler: 'removeDomain',
 | 
			
		||||
	},
 | 
			
		||||
	'-',
 | 
			
		||||
	'order-menu', // placeholder, filled in initComponent
 | 
			
		||||
	'-',
 | 
			
		||||
	{
 | 
			
		||||
	    xtype: 'displayfield',
 | 
			
		||||
	    value: gettext('Using Account') + ':',
 | 
			
		||||
	    bind: {
 | 
			
		||||
		hidden: '{!accountsAvailable}',
 | 
			
		||||
	    },
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
	    xtype: 'displayfield',
 | 
			
		||||
	    reference: 'accounttext',
 | 
			
		||||
	    renderer: (val) => val || Proxmox.Utils.NoneText,
 | 
			
		||||
	    bind: {
 | 
			
		||||
		value: '{account}',
 | 
			
		||||
		hidden: '{accountTextHidden}',
 | 
			
		||||
	    },
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
	    xtype: 'pmxACMEAccountSelector',
 | 
			
		||||
	    hidden: true,
 | 
			
		||||
	    reference: 'accountselector',
 | 
			
		||||
	    cbind: {
 | 
			
		||||
		url: '{accountUrl}',
 | 
			
		||||
	    },
 | 
			
		||||
	    bind: {
 | 
			
		||||
		value: '{account}',
 | 
			
		||||
		hidden: '{accountValueHidden}',
 | 
			
		||||
	    },
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
	    xtype: 'button',
 | 
			
		||||
	    iconCls: 'fa black fa-pencil',
 | 
			
		||||
	    baseCls: 'x-plain',
 | 
			
		||||
	    userCls: 'pointer',
 | 
			
		||||
	    bind: {
 | 
			
		||||
		iconCls: '{editBtnIcon}',
 | 
			
		||||
		hidden: '{!accountsAvailable}',
 | 
			
		||||
	    },
 | 
			
		||||
	    handler: 'toggleEditAccount',
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
	    xtype: 'displayfield',
 | 
			
		||||
	    value: gettext('No Account available.'),
 | 
			
		||||
	    bind: {
 | 
			
		||||
		hidden: '{accountsAvailable}',
 | 
			
		||||
	    },
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
	    xtype: 'button',
 | 
			
		||||
	    hidden: true,
 | 
			
		||||
	    reference: 'accountlink',
 | 
			
		||||
	    text: gettext('Add ACME Account'),
 | 
			
		||||
	    bind: {
 | 
			
		||||
		hidden: '{accountsAvailable}',
 | 
			
		||||
	    },
 | 
			
		||||
	    handler: 'addAccount',
 | 
			
		||||
	},
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    updateStore: function(store, records, success) {
 | 
			
		||||
	let me = this;
 | 
			
		||||
	let data = [];
 | 
			
		||||
	let rec;
 | 
			
		||||
	if (success && records.length > 0) {
 | 
			
		||||
	    rec = records[0];
 | 
			
		||||
	} else {
 | 
			
		||||
	    rec = {
 | 
			
		||||
		data: {},
 | 
			
		||||
	    };
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	me.nodeconfig = rec.data; // save nodeconfig for updates
 | 
			
		||||
 | 
			
		||||
	let account = 'default';
 | 
			
		||||
 | 
			
		||||
	if (rec.data.acme) {
 | 
			
		||||
	    let obj = Proxmox.Utils.parseACME(rec.data.acme);
 | 
			
		||||
	    (obj.domains || []).forEach(domain => {
 | 
			
		||||
		if (domain === '') return;
 | 
			
		||||
		let record = {
 | 
			
		||||
		    domain,
 | 
			
		||||
		    type: 'standalone',
 | 
			
		||||
		    configkey: 'acme',
 | 
			
		||||
		};
 | 
			
		||||
		data.push(record);
 | 
			
		||||
	    });
 | 
			
		||||
 | 
			
		||||
	    if (obj.account) {
 | 
			
		||||
		account = obj.account;
 | 
			
		||||
	    }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let vm = me.getViewModel();
 | 
			
		||||
	let oldaccount = vm.get('account');
 | 
			
		||||
 | 
			
		||||
	// account changed, and we do not edit currently, load again to verify
 | 
			
		||||
	if (oldaccount !== account && !vm.get('accountEditable')) {
 | 
			
		||||
	    vm.set('configaccount', account);
 | 
			
		||||
	    me.lookup('accountselector').store.load();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (let i = 0; i < Proxmox.Utils.acmedomain_count; i++) {
 | 
			
		||||
	    let acmedomain = rec.data[`acmedomain${i}`];
 | 
			
		||||
	    if (!acmedomain) continue;
 | 
			
		||||
 | 
			
		||||
	    let record = Proxmox.Utils.parsePropertyString(acmedomain, 'domain');
 | 
			
		||||
	    record.type = record.plugin ? 'dns' : 'standalone';
 | 
			
		||||
	    record.configkey = `acmedomain${i}`;
 | 
			
		||||
	    data.push(record);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vm.set('domaincount', data.length);
 | 
			
		||||
	me.store.loadData(data, false);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    listeners: {
 | 
			
		||||
	itemdblclick: 'editDomain',
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    columns: [
 | 
			
		||||
	{
 | 
			
		||||
	    dataIndex: 'domain',
 | 
			
		||||
	    flex: 5,
 | 
			
		||||
	    text: gettext('Domain'),
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
	    dataIndex: 'usage',
 | 
			
		||||
	    flex: 1,
 | 
			
		||||
	    text: gettext('Usage'),
 | 
			
		||||
	    bind: {
 | 
			
		||||
		hidden: '{!hasUsage}',
 | 
			
		||||
	    },
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
	    dataIndex: 'type',
 | 
			
		||||
	    flex: 1,
 | 
			
		||||
	    text: gettext('Type'),
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
	    dataIndex: 'plugin',
 | 
			
		||||
	    flex: 1,
 | 
			
		||||
	    text: gettext('Plugin'),
 | 
			
		||||
	},
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    initComponent: function() {
 | 
			
		||||
	let me = this;
 | 
			
		||||
 | 
			
		||||
	if (!me.acmeUrl) {
 | 
			
		||||
	    throw "no acmeUrl given";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!me.url) {
 | 
			
		||||
	    throw "no url given";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!me.nodename) {
 | 
			
		||||
	    throw "no nodename given";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!me.domainUsages && !me.orderUrl) {
 | 
			
		||||
	    throw "neither domainUsages nor orderUrl given";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	me.rstore = Ext.create('Proxmox.data.UpdateStore', {
 | 
			
		||||
	    interval: 10 * 1000,
 | 
			
		||||
	    autoStart: true,
 | 
			
		||||
	    storeid: `proxmox-node-domains-${me.nodename}`,
 | 
			
		||||
	    proxy: {
 | 
			
		||||
		type: 'proxmox',
 | 
			
		||||
		url: `/api2/json/${me.url}`,
 | 
			
		||||
	    },
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	me.store = Ext.create('Ext.data.Store', {
 | 
			
		||||
	    model: 'proxmox-acme-domains',
 | 
			
		||||
	    sorters: 'domain',
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	if (me.domainUsages) {
 | 
			
		||||
	    let items = [];
 | 
			
		||||
 | 
			
		||||
	    for (const cert of me.domainUsages) {
 | 
			
		||||
		if (!cert.name) {
 | 
			
		||||
		    throw "missing certificate url";
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!cert.url) {
 | 
			
		||||
		    throw "missing certificate url";
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		items.push({
 | 
			
		||||
		    text: Ext.String.format('Order {0} Certificate Now', cert.name),
 | 
			
		||||
		    handler: function() {
 | 
			
		||||
			return me.getController().order(cert);
 | 
			
		||||
		    },
 | 
			
		||||
		});
 | 
			
		||||
	    }
 | 
			
		||||
	    me.tbar.splice(
 | 
			
		||||
		me.tbar.indexOf("order-menu"),
 | 
			
		||||
		1,
 | 
			
		||||
		{
 | 
			
		||||
		    text: gettext('Order Certificates Now'),
 | 
			
		||||
		    menu: {
 | 
			
		||||
			xtype: 'menu',
 | 
			
		||||
			items,
 | 
			
		||||
		    },
 | 
			
		||||
		},
 | 
			
		||||
	    );
 | 
			
		||||
	} else {
 | 
			
		||||
	    me.tbar.splice(
 | 
			
		||||
		me.tbar.indexOf("order-menu"),
 | 
			
		||||
		1,
 | 
			
		||||
		{
 | 
			
		||||
		    xtype: 'button',
 | 
			
		||||
		    reference: 'order',
 | 
			
		||||
		    text: gettext('Order Certificates Now'),
 | 
			
		||||
		    bind: {
 | 
			
		||||
			disabled: '{!canOrder}',
 | 
			
		||||
		    },
 | 
			
		||||
		    handler: function() {
 | 
			
		||||
			return me.getController().order();
 | 
			
		||||
		    },
 | 
			
		||||
		},
 | 
			
		||||
	    );
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	me.callParent();
 | 
			
		||||
	me.getViewModel().set('hasUsage', !!me.domainUsages);
 | 
			
		||||
	me.mon(me.rstore, 'load', 'updateStore', me);
 | 
			
		||||
	Proxmox.Utils.monStoreErrors(me, me.rstore);
 | 
			
		||||
	me.on('destroy', me.rstore.stopUpdate, me.rstore);
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										213
									
								
								src/window/ACMEDomains.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								src/window/ACMEDomains.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,213 @@
 | 
			
		||||
Ext.define('Proxmox.window.ACMEDomainEdit', {
 | 
			
		||||
    extend: 'Proxmox.window.Edit',
 | 
			
		||||
    xtype: 'pmxACMEDomainEdit',
 | 
			
		||||
    mixins: ['Proxmox.Mixin.CBind'],
 | 
			
		||||
 | 
			
		||||
    subject: gettext('Domain'),
 | 
			
		||||
    isCreate: false,
 | 
			
		||||
    width: 450,
 | 
			
		||||
    //onlineHelp: 'sysadmin_certificate_management',
 | 
			
		||||
 | 
			
		||||
    acmeUrl: undefined,
 | 
			
		||||
 | 
			
		||||
    // config url
 | 
			
		||||
    url: undefined,
 | 
			
		||||
 | 
			
		||||
    // For PMG the we have multiple certificates, so we have a "usage" attribute & column.
 | 
			
		||||
    domainUsages: undefined,
 | 
			
		||||
 | 
			
		||||
    cbindData: function(config) {
 | 
			
		||||
	let me = this;
 | 
			
		||||
	return {
 | 
			
		||||
	    pluginsUrl: `/api2/json/${me.acmeUrl}/plugins`,
 | 
			
		||||
	    hasUsage: !!me.domainUsages,
 | 
			
		||||
	};
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    items: [
 | 
			
		||||
	{
 | 
			
		||||
	    xtype: 'inputpanel',
 | 
			
		||||
	    onGetValues: function(values) {
 | 
			
		||||
		let me = this;
 | 
			
		||||
		let win = me.up('pmxACMEDomainEdit');
 | 
			
		||||
		let nodeconfig = win.nodeconfig;
 | 
			
		||||
		let olddomain = win.domain || {};
 | 
			
		||||
 | 
			
		||||
		let params = {
 | 
			
		||||
		    digest: nodeconfig.digest,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		let configkey = olddomain.configkey;
 | 
			
		||||
		let acmeObj = Proxmox.Utils.parseACME(nodeconfig.acme);
 | 
			
		||||
 | 
			
		||||
		let find_free_slot = () => {
 | 
			
		||||
		    for (let i = 0; i < Proxmox.Utils.acmedomain_count; i++) {
 | 
			
		||||
			if (nodeconfig[`acmedomain${i}`] === undefined) {
 | 
			
		||||
			    return `acmedomain${i}`;
 | 
			
		||||
			}
 | 
			
		||||
		    }
 | 
			
		||||
		    throw "too many domains configured";
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		// If we have a 'usage' property (pmg), we only use the `acmedomainX` config keys.
 | 
			
		||||
		if (win.domainUsages) {
 | 
			
		||||
		    if (!configkey || configkey === 'acme') {
 | 
			
		||||
			configkey = find_free_slot();
 | 
			
		||||
		    }
 | 
			
		||||
		    delete values.type;
 | 
			
		||||
		    params[configkey] = Proxmox.Utils.printPropertyString(values, 'domain');
 | 
			
		||||
		    return params;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Otherwise we put the standalone entries into the `domains` list of the `acme`
 | 
			
		||||
		// property string.
 | 
			
		||||
 | 
			
		||||
		// Then insert the domain depending on its type:
 | 
			
		||||
		if (values.type === 'dns') {
 | 
			
		||||
		    if (!olddomain.configkey || olddomain.configkey === 'acme') {
 | 
			
		||||
			configkey = find_free_slot();
 | 
			
		||||
			if (olddomain.domain) {
 | 
			
		||||
			    // we have to remove the domain from the acme domainlist
 | 
			
		||||
			    Proxmox.Utils.remove_domain_from_acme(acmeObj, olddomain.domain);
 | 
			
		||||
			    params.acme = Proxmox.Utils.printACME(acmeObj);
 | 
			
		||||
			}
 | 
			
		||||
		    }
 | 
			
		||||
 | 
			
		||||
		    delete values.type;
 | 
			
		||||
		    params[configkey] = Proxmox.Utils.printPropertyString(values, 'domain');
 | 
			
		||||
		} else {
 | 
			
		||||
		    if (olddomain.configkey && olddomain.configkey !== 'acme') {
 | 
			
		||||
			// delete the old dns entry, unless we need to declare its usage:
 | 
			
		||||
			params.delete = [olddomain.configkey];
 | 
			
		||||
		    }
 | 
			
		||||
 | 
			
		||||
		    // add new, remove old and make entries unique
 | 
			
		||||
		    Proxmox.Utils.add_domain_to_acme(acmeObj, values.domain);
 | 
			
		||||
		    if (olddomain.domain !== values.domain) {
 | 
			
		||||
			Proxmox.Utils.remove_domain_from_acme(acmeObj, olddomain.domain);
 | 
			
		||||
		    }
 | 
			
		||||
		    params.acme = Proxmox.Utils.printACME(acmeObj);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return params;
 | 
			
		||||
	    },
 | 
			
		||||
	    items: [
 | 
			
		||||
		{
 | 
			
		||||
		    xtype: 'proxmoxKVComboBox',
 | 
			
		||||
		    name: 'type',
 | 
			
		||||
		    fieldLabel: gettext('Challenge Type'),
 | 
			
		||||
		    allowBlank: false,
 | 
			
		||||
		    value: 'standalone',
 | 
			
		||||
		    comboItems: [
 | 
			
		||||
			['standalone', 'HTTP'],
 | 
			
		||||
			['dns', 'DNS'],
 | 
			
		||||
		    ],
 | 
			
		||||
		    validator: function(value) {
 | 
			
		||||
			let me = this;
 | 
			
		||||
			let win = me.up('pmxACMEDomainEdit');
 | 
			
		||||
			let oldconfigkey = win.domain ? win.domain.configkey : undefined;
 | 
			
		||||
			let val = me.getValue();
 | 
			
		||||
			if (val === 'dns' && (!oldconfigkey || oldconfigkey === 'acme')) {
 | 
			
		||||
			    // we have to check if there is a 'acmedomain' slot left
 | 
			
		||||
			    let found = false;
 | 
			
		||||
			    for (let i = 0; i < Proxmox.Utils.acmedomain_count; i++) {
 | 
			
		||||
				if (!win.nodeconfig[`acmedomain${i}`]) {
 | 
			
		||||
				    found = true;
 | 
			
		||||
				}
 | 
			
		||||
			    }
 | 
			
		||||
			    if (!found) {
 | 
			
		||||
				return gettext('Only 5 Domains with type DNS can be configured');
 | 
			
		||||
			    }
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return true;
 | 
			
		||||
		    },
 | 
			
		||||
		    listeners: {
 | 
			
		||||
			change: function(cb, value) {
 | 
			
		||||
			    let me = this;
 | 
			
		||||
			    let view = me.up('pmxACMEDomainEdit');
 | 
			
		||||
			    let pluginField = view.down('field[name=plugin]');
 | 
			
		||||
			    pluginField.setDisabled(value !== 'dns');
 | 
			
		||||
			    pluginField.setHidden(value !== 'dns');
 | 
			
		||||
			},
 | 
			
		||||
		    },
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
		    xtype: 'hidden',
 | 
			
		||||
		    name: 'alias',
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
		    xtype: 'pmxACMEPluginSelector',
 | 
			
		||||
		    name: 'plugin',
 | 
			
		||||
		    disabled: true,
 | 
			
		||||
		    hidden: true,
 | 
			
		||||
		    allowBlank: false,
 | 
			
		||||
		    cbind: {
 | 
			
		||||
			url: '{pluginsUrl}',
 | 
			
		||||
		    },
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
		    xtype: 'proxmoxtextfield',
 | 
			
		||||
		    name: 'domain',
 | 
			
		||||
		    allowBlank: false,
 | 
			
		||||
		    vtype: 'DnsName',
 | 
			
		||||
		    value: '',
 | 
			
		||||
		    fieldLabel: gettext('Domain'),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
		    xtype: 'combobox',
 | 
			
		||||
		    name: 'usage',
 | 
			
		||||
		    multiSelect: true,
 | 
			
		||||
		    editable: false,
 | 
			
		||||
		    fieldLabel: gettext('Usage'),
 | 
			
		||||
		    cbind: {
 | 
			
		||||
			hidden: '{!hasUsage}',
 | 
			
		||||
			allowBlank: '{!hasUsage}',
 | 
			
		||||
		    },
 | 
			
		||||
		    fields: ['usage', 'name'],
 | 
			
		||||
		    displayField: 'name',
 | 
			
		||||
		    valueField: 'usage',
 | 
			
		||||
		    store: {
 | 
			
		||||
			data: [
 | 
			
		||||
			    { usage: 'api', name: 'API' },
 | 
			
		||||
			    { usage: 'smtp', name: 'SMTP' },
 | 
			
		||||
			],
 | 
			
		||||
		    },
 | 
			
		||||
		},
 | 
			
		||||
	    ],
 | 
			
		||||
	},
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    initComponent: function() {
 | 
			
		||||
	let me = this;
 | 
			
		||||
 | 
			
		||||
	if (!me.url) {
 | 
			
		||||
	    throw 'no url given';
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!me.acmeUrl) {
 | 
			
		||||
	    throw 'no acmeUrl given';
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!me.nodeconfig) {
 | 
			
		||||
	    throw 'no nodeconfig given';
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	me.isCreate = !me.domain;
 | 
			
		||||
	if (me.isCreate) {
 | 
			
		||||
	    me.domain = `${Proxmox.NodeName}.`; // TODO: FQDN of node
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	me.callParent();
 | 
			
		||||
 | 
			
		||||
	if (!me.isCreate) {
 | 
			
		||||
	    let values = { ...me.domain };
 | 
			
		||||
	    if (Ext.isDefined(values.usage)) {
 | 
			
		||||
		values.usage = values.usage.split(';');
 | 
			
		||||
	    }
 | 
			
		||||
	    me.setValues(values);
 | 
			
		||||
	} else {
 | 
			
		||||
	    me.setValues({ domain: me.domain });
 | 
			
		||||
	}
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user