mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-05-19 12:06:17 +00:00

instead of using API2Request manually, just reload the store of the accountselector and check if the configured account is in it this should fix the spurious loading mask of the panel when loading the accounts Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
733 lines
16 KiB
JavaScript
733 lines
16 KiB
JavaScript
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: 'proxmoxtextfield',
|
|
fieldLabel: gettext('Name'),
|
|
name: 'name',
|
|
emptyText: 'default',
|
|
allowBlank: true,
|
|
},
|
|
{
|
|
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.ACMEDomainEdit', {
|
|
extend: 'Proxmox.window.Edit',
|
|
alias: 'widget.pveACMEDomainEdit',
|
|
|
|
subject: gettext('Domain'),
|
|
isCreate: false,
|
|
width: 450,
|
|
onlineHelp: 'sysadmin_certificate_management',
|
|
|
|
items: [
|
|
{
|
|
xtype: 'inputpanel',
|
|
onGetValues: function(values) {
|
|
let me = this;
|
|
let win = me.up('pveACMEDomainEdit');
|
|
let nodeconfig = win.nodeconfig;
|
|
let olddomain = win.domain || {};
|
|
|
|
let params = {
|
|
digest: nodeconfig.digest,
|
|
};
|
|
|
|
let configkey = olddomain.configkey;
|
|
let acmeObj = PVE.Parser.parseACME(nodeconfig.acme);
|
|
|
|
if (values.type === 'dns') {
|
|
if (!olddomain.configkey || olddomain.configkey === 'acme') {
|
|
// look for first free slot
|
|
for (let i = 0; i < PVE.Utils.acmedomain_count; i++) {
|
|
if (nodeconfig[`acmedomain${i}`] === undefined) {
|
|
configkey = `acmedomain${i}`;
|
|
break;
|
|
}
|
|
}
|
|
if (olddomain.domain) {
|
|
// we have to remove the domain from the acme domainlist
|
|
PVE.Utils.remove_domain_from_acme(acmeObj, olddomain.domain);
|
|
params.acme = PVE.Parser.printACME(acmeObj);
|
|
}
|
|
}
|
|
|
|
delete values.type;
|
|
params[configkey] = PVE.Parser.printPropertyString(values, 'domain');
|
|
} else {
|
|
if (olddomain.configkey && olddomain.configkey !== 'acme') {
|
|
// delete the old dns entry
|
|
params.delete = [olddomain.configkey];
|
|
}
|
|
|
|
// add new, remove old and make entries unique
|
|
PVE.Utils.add_domain_to_acme(acmeObj, values.domain);
|
|
PVE.Utils.remove_domain_from_acme(acmeObj, olddomain.domain);
|
|
params.acme = PVE.Parser.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('pveACMEDomainEdit');
|
|
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 < PVE.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('pveACMEDomainEdit');
|
|
let pluginField = view.down('field[name=plugin]');
|
|
pluginField.setDisabled(value !== 'dns');
|
|
pluginField.setHidden(value !== 'dns');
|
|
},
|
|
},
|
|
},
|
|
{
|
|
xtype: 'hidden',
|
|
name: 'alias',
|
|
},
|
|
{
|
|
xtype: 'pveACMEPluginSelector',
|
|
name: 'plugin',
|
|
disabled: true,
|
|
hidden: true,
|
|
allowBlank: false,
|
|
},
|
|
{
|
|
xtype: 'proxmoxtextfield',
|
|
name: 'domain',
|
|
allowBlank: false,
|
|
vtype: 'DnsName',
|
|
value: '',
|
|
fieldLabel: gettext('Domain'),
|
|
},
|
|
],
|
|
},
|
|
],
|
|
|
|
initComponent: function() {
|
|
let me = this;
|
|
|
|
if (!me.nodename) {
|
|
throw 'no nodename given';
|
|
}
|
|
|
|
if (!me.nodeconfig) {
|
|
throw 'no nodeconfig given';
|
|
}
|
|
|
|
me.isCreate = !me.domain;
|
|
if (me.isCreate) {
|
|
me.domain = `${me.nodename}.`; // TODO: FQDN of node
|
|
}
|
|
|
|
me.url = `/api2/extjs/nodes/${me.nodename}/config`;
|
|
|
|
me.callParent();
|
|
|
|
if (!me.isCreate) {
|
|
me.setValues(me.domain);
|
|
} else {
|
|
me.setValues({ domain: me.domain });
|
|
}
|
|
},
|
|
});
|
|
|
|
Ext.define('pve-acme-domains', {
|
|
extend: 'Ext.data.Model',
|
|
fields: ['domain', 'type', 'alias', 'plugin', 'configkey'],
|
|
idProperty: 'domain',
|
|
});
|
|
|
|
Ext.define('PVE.node.ACME', {
|
|
extend: 'Ext.grid.Panel',
|
|
alias: 'widget.pveACMEView',
|
|
|
|
margin: '10 0 0 0',
|
|
title: 'ACME',
|
|
|
|
emptyText: gettext('No Domains configured'),
|
|
|
|
viewModel: {
|
|
data: {
|
|
account: undefined, // the account we display
|
|
configaccount: undefined, // the account set in the config
|
|
accountEditable: false,
|
|
accountsAvailable: false,
|
|
},
|
|
|
|
formulas: {
|
|
editBtnIcon: (get) => 'fa black fa-' + (get('accountEditable') ? 'check' : 'pencil'),
|
|
accountTextHidden: (get) => get('accountEditable') || !get('accountsAvailable'),
|
|
accountValueHidden: (get) => !get('accountEditable') || !get('accountsAvailable'),
|
|
},
|
|
},
|
|
|
|
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('PVE.node.ACMEDomainEdit', {
|
|
nodename: view.nodename,
|
|
nodeconfig: view.nodeconfig,
|
|
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('PVE.node.ACMEDomainEdit', {
|
|
nodename: view.nodename,
|
|
nodeconfig: view.nodeconfig,
|
|
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 = PVE.Parser.parseACME(view.nodeconfig.acme);
|
|
PVE.Utils.remove_domain_from_acme(acme, rec.domain);
|
|
params.acme = PVE.Parser.printACME(acme);
|
|
}
|
|
|
|
Proxmox.Utils.API2Request({
|
|
method: 'PUT',
|
|
url: `/nodes/${view.nodename}/config`,
|
|
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 = PVE.Parser.parseACME(view.nodeconfig.acme);
|
|
acme.account = account;
|
|
params.acme = PVE.Parser.printACME(acme);
|
|
|
|
Proxmox.Utils.API2Request({
|
|
method: 'PUT',
|
|
waitMsgTarget: view,
|
|
url: `/nodes/${view.nodename}/config`,
|
|
params,
|
|
success: function(response, opt) {
|
|
if (Ext.isFunction(callback)) {
|
|
callback();
|
|
}
|
|
},
|
|
failure: function(response, opt) {
|
|
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
|
|
},
|
|
});
|
|
},
|
|
|
|
order: function() {
|
|
let me = this;
|
|
let view = me.getView();
|
|
|
|
Proxmox.Utils.API2Request({
|
|
method: 'POST',
|
|
params: {
|
|
force: 1,
|
|
},
|
|
url: `/nodes/${view.nodename}/certificates/acme/certificate`,
|
|
success: function(response, opt) {
|
|
Ext.create('Proxmox.window.TaskViewer', {
|
|
upid: response.result.data,
|
|
taskDone: function(success) {
|
|
me.orderFinished(success);
|
|
},
|
|
}).show();
|
|
},
|
|
failure: function(response, opt) {
|
|
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
|
|
},
|
|
});
|
|
},
|
|
|
|
orderFinished: 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);
|
|
},
|
|
|
|
reload: function() {
|
|
let me = this;
|
|
let view = me.getView();
|
|
view.rstore.load();
|
|
},
|
|
|
|
addAccount: function() {
|
|
let me = this;
|
|
Ext.create('PVE.node.ACMEAccountCreate', {
|
|
autoShow: true,
|
|
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',
|
|
},
|
|
'-',
|
|
{
|
|
xtype: 'button',
|
|
reference: 'order',
|
|
text: gettext('Order Certificates Now'),
|
|
bind: {
|
|
disabled: '{!accountsAvailable}',
|
|
},
|
|
handler: 'order',
|
|
},
|
|
'-',
|
|
{
|
|
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: 'pveACMEAccountSelector',
|
|
hidden: true,
|
|
reference: 'accountselector',
|
|
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 = PVE.Parser.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 < PVE.Utils.acmedomain_count; i++) {
|
|
let acmedomain = rec.data[`acmedomain${i}`];
|
|
if (!acmedomain) continue;
|
|
|
|
let record = PVE.Parser.parsePropertyString(acmedomain, 'domain');
|
|
record.type = 'dns';
|
|
record.configkey = `acmedomain${i}`;
|
|
data.push(record);
|
|
}
|
|
|
|
me.store.loadData(data, false);
|
|
},
|
|
|
|
listeners: {
|
|
itemdblclick: 'editDomain',
|
|
},
|
|
|
|
columns: [
|
|
{
|
|
dataIndex: 'domain',
|
|
flex: 5,
|
|
text: gettext('Domain'),
|
|
},
|
|
{
|
|
dataIndex: 'type',
|
|
flex: 1,
|
|
text: gettext('Type'),
|
|
},
|
|
{
|
|
dataIndex: 'plugin',
|
|
flex: 1,
|
|
text: gettext('Plugin'),
|
|
},
|
|
],
|
|
|
|
initComponent: function() {
|
|
var me = this;
|
|
|
|
if (!me.nodename) {
|
|
throw "no nodename given";
|
|
}
|
|
|
|
me.rstore = Ext.create('Proxmox.data.UpdateStore', {
|
|
interval: 10 * 1000,
|
|
autoStart: true,
|
|
storeid: `pve-node-domains-${me.nodename}`,
|
|
proxy: {
|
|
type: 'proxmox',
|
|
url: `/api2/json/nodes/${me.nodename}/config`,
|
|
},
|
|
});
|
|
|
|
me.store = Ext.create('Ext.data.Store', {
|
|
model: 'pve-acme-domains',
|
|
sorters: 'domain',
|
|
});
|
|
|
|
me.callParent();
|
|
me.mon(me.rstore, 'load', 'updateStore', me);
|
|
Proxmox.Utils.monStoreErrors(me, me.rstore);
|
|
},
|
|
});
|