]>
git.proxmox.com Git - pve-manager.git/blob - www/manager6/node/ACME.js
d2863a7c814253dfaecdd7350f0a5899433503e2
1 Ext
.define('PVE.node.ACMEAccountCreate', {
2 extend
: 'Proxmox.window.Edit',
3 mixins
: ['Proxmox.Mixin.CBind'],
6 title
: gettext('Register Account'),
9 submitText
: gettext('Register'),
10 url
: '/cluster/acme/account',
13 referenceHolder
: true,
14 onlineHelp
: "sysadmin_certs_acme_account",
18 customDirectory
: false,
24 xtype
: 'proxmoxtextfield',
25 fieldLabel
: gettext('Account Name'),
28 emptyText
: (get) => get('defaultExists') ? '' : 'default',
29 allowBlank
: (get) => !get('defaultExists'),
37 fieldLabel
: gettext('E-Mail'),
40 xtype
: 'proxmoxComboGrid',
41 notFoundIsValid
: true,
46 fieldLabel
: gettext('ACME Directory'),
50 this.add({ name
: gettext("Custom"), url
: '' });
54 fields
: ['name', 'url'],
58 url
: '/api2/json/cluster/acme/directories',
64 header
: gettext('Name'),
69 header
: gettext('URL'),
76 change: function(combogrid
, value
) {
79 let vm
= me
.up('window').getViewModel();
80 let dirField
= me
.up('window').lookupReference('directoryInput');
81 let tosButton
= me
.up('window').lookupReference('queryTos');
83 let isCustom
= combogrid
.getSelection().get('name') === gettext("Custom");
84 vm
.set('customDirectory', isCustom
);
86 dirField
.setValue(value
);
91 me
.up('window').clearToSFields();
97 xtype
: 'fieldcontainer',
99 fieldLabel
: gettext('URL'),
101 hidden
: '{!customDirectory}',
105 xtype
: 'proxmoxtextfield',
107 reference
: 'directoryInput',
111 change: function(textbox
, value
) {
113 me
.up('window').clearToSFields();
118 xtype
: 'proxmoxButton',
120 reference
: 'queryTos',
121 text
: gettext('Query URL'),
123 click: function(button
) {
126 let w
= me
.up('window');
127 let disp
= w
.down('#tos_url_display');
128 let field
= w
.down('#tos_url');
129 let checkbox
= w
.down('#tos_checkbox');
130 let value
= w
.lookupReference('directoryInput').getValue();
136 disp
.setValue(gettext("Loading"));
139 Proxmox
.Utils
.API2Request({
140 url
: '/cluster/acme/meta',
145 success: function(response
, opt
) {
146 if (response
.result
.data
.termsOfService
) {
147 field
.setValue(response
.result
.data
.termsOfService
);
148 disp
.setValue(response
.result
.data
.termsOfService
);
149 checkbox
.setHidden(false);
151 checkbox
.setValue(false);
152 disp
.setValue("No terms of service agreement required");
155 failure: function(response
, opt
) {
156 disp
.setValue(undefined);
157 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
166 xtype
: 'displayfield',
167 itemId
: 'tos_url_display',
168 renderer
: PVE
.Utils
.render_optional_url
,
169 name
: 'tos_url_display',
177 xtype
: 'proxmoxcheckbox',
178 itemId
: 'tos_checkbox',
179 boxLabel
: gettext('Accept TOS'),
181 validateValue: function(value
) {
182 if (value
&& this.checked
) {
190 clearToSFields: function() {
193 let disp
= me
.down('#tos_url_display');
194 let field
= me
.down('#tos_url');
195 let checkbox
= me
.down('#tos_checkbox');
197 disp
.setValue("Terms of service not fetched yet");
198 field
.setValue(undefined);
199 checkbox
.setValue(undefined);
200 checkbox
.setHidden(true);
205 Ext
.define('PVE.node.ACMEAccountView', {
206 extend
: 'Proxmox.window.Edit',
213 title
: gettext('Account'),
217 xtype
: 'displayfield',
218 fieldLabel
: gettext('E-Mail'),
222 xtype
: 'displayfield',
223 fieldLabel
: gettext('Created'),
227 xtype
: 'displayfield',
228 fieldLabel
: gettext('Status'),
232 xtype
: 'displayfield',
233 fieldLabel
: gettext('Directory'),
234 renderer
: PVE
.Utils
.render_optional_url
,
238 xtype
: 'displayfield',
239 fieldLabel
: gettext('Terms of Services'),
240 renderer
: PVE
.Utils
.render_optional_url
,
245 initComponent: function() {
248 if (!me
.accountname
) {
249 throw "no account name defined";
252 me
.url
= '/cluster/acme/account/' + me
.accountname
;
256 // hide OK/Reset button, because we just want to show data
257 me
.down('toolbar[dock=bottom]').setVisible(false);
260 success: function(response
) {
261 var data
= response
.result
.data
;
262 data
.email
= data
.account
.contact
[0];
263 data
.createdAt
= data
.account
.createdAt
;
264 data
.status
= data
.account
.status
;
271 Ext
.define('PVE.node.ACMEDomainEdit', {
272 extend
: 'Proxmox.window.Edit',
273 alias
: 'widget.pveACMEDomainEdit',
275 subject
: gettext('Domain'),
278 onlineHelp
: 'sysadmin_certificate_management',
283 onGetValues: function(values
) {
285 let win
= me
.up('pveACMEDomainEdit');
286 let nodeconfig
= win
.nodeconfig
;
287 let olddomain
= win
.domain
|| {};
290 digest
: nodeconfig
.digest
,
293 let configkey
= olddomain
.configkey
;
294 let acmeObj
= PVE
.Parser
.parseACME(nodeconfig
.acme
);
296 if (values
.type
=== 'dns') {
297 if (!olddomain
.configkey
|| olddomain
.configkey
=== 'acme') {
298 // look for first free slot
299 for (let i
= 0; i
< PVE
.Utils
.acmedomain_count
; i
++) {
300 if (nodeconfig
[`acmedomain${i}`] === undefined) {
301 configkey
= `acmedomain${i}`;
305 if (olddomain
.domain
) {
306 // we have to remove the domain from the acme domainlist
307 PVE
.Utils
.remove_domain_from_acme(acmeObj
, olddomain
.domain
);
308 params
.acme
= PVE
.Parser
.printACME(acmeObj
);
313 params
[configkey
] = PVE
.Parser
.printPropertyString(values
, 'domain');
315 if (olddomain
.configkey
&& olddomain
.configkey
!== 'acme') {
316 // delete the old dns entry
317 params
.delete = [olddomain
.configkey
];
320 // add new, remove old and make entries unique
321 PVE
.Utils
.add_domain_to_acme(acmeObj
, values
.domain
);
322 PVE
.Utils
.remove_domain_from_acme(acmeObj
, olddomain
.domain
);
323 params
.acme
= PVE
.Parser
.printACME(acmeObj
);
330 xtype
: 'proxmoxKVComboBox',
332 fieldLabel
: gettext('Challenge Type'),
336 ['standalone', 'HTTP'],
339 validator: function(value
) {
341 let win
= me
.up('pveACMEDomainEdit');
342 let oldconfigkey
= win
.domain
? win
.domain
.configkey
: undefined;
343 let val
= me
.getValue();
344 if (val
=== 'dns' && (!oldconfigkey
|| oldconfigkey
=== 'acme')) {
345 // we have to check if there is a 'acmedomain' slot left
347 for (let i
= 0; i
< PVE
.Utils
.acmedomain_count
; i
++) {
348 if (!win
.nodeconfig
[`acmedomain${i}`]) {
353 return gettext('Only 5 Domains with type DNS can be configured');
360 change: function(cb
, value
) {
362 let view
= me
.up('pveACMEDomainEdit');
363 let pluginField
= view
.down('field[name=plugin]');
364 pluginField
.setDisabled(value
!== 'dns');
365 pluginField
.setHidden(value
!== 'dns');
374 xtype
: 'pveACMEPluginSelector',
381 xtype
: 'proxmoxtextfield',
386 fieldLabel
: gettext('Domain'),
392 initComponent: function() {
396 throw 'no nodename given';
399 if (!me
.nodeconfig
) {
400 throw 'no nodeconfig given';
403 me
.isCreate
= !me
.domain
;
405 me
.domain
= `${me.nodename}.`; // TODO: FQDN of node
408 me
.url
= `/api2/extjs/nodes/${me.nodename}/config`;
413 me
.setValues(me
.domain
);
415 me
.setValues({ domain
: me
.domain
});
420 Ext
.define('pve-acme-domains', {
421 extend
: 'Ext.data.Model',
422 fields
: ['domain', 'type', 'alias', 'plugin', 'configkey'],
423 idProperty
: 'domain',
426 Ext
.define('PVE.node.ACME', {
427 extend
: 'Ext.grid.Panel',
428 alias
: 'widget.pveACMEView',
433 emptyText
: gettext('No Domains configured'),
438 account
: undefined, // the account we display
439 configaccount
: undefined, // the account set in the config
440 accountEditable
: false,
441 accountsAvailable
: false,
445 canOrder
: (get) => !!get('account') && get('domaincount') > 0,
446 editBtnIcon
: (get) => 'fa black fa-' + (get('accountEditable') ? 'check' : 'pencil'),
447 editBtnText
: (get) => get('accountEditable') ? gettext('Apply') : gettext('Edit'),
448 accountTextHidden
: (get) => get('accountEditable') || !get('accountsAvailable'),
449 accountValueHidden
: (get) => !get('accountEditable') || !get('accountsAvailable'),
454 xclass
: 'Ext.app.ViewController',
456 init: function(view
) {
457 let accountSelector
= this.lookup('accountselector');
458 accountSelector
.store
.on('load', this.onAccountsLoad
, this);
461 onAccountsLoad: function(store
, records
, success
) {
463 let vm
= me
.getViewModel();
464 let configaccount
= vm
.get('configaccount');
465 vm
.set('accountsAvailable', records
.length
> 0);
466 if (me
.autoChangeAccount
&& records
.length
> 0) {
467 me
.changeAccount(records
[0].data
.name
, () => {
468 vm
.set('accountEditable', false);
471 me
.autoChangeAccount
= false;
472 } else if (configaccount
) {
473 if (store
.findExact('name', configaccount
) !== -1) {
474 vm
.set('account', configaccount
);
476 vm
.set('account', null);
481 addDomain: function() {
483 let view
= me
.getView();
485 Ext
.create('PVE.node.ACMEDomainEdit', {
486 nodename
: view
.nodename
,
487 nodeconfig
: view
.nodeconfig
,
488 apiCallDone: function() {
494 editDomain: function() {
496 let view
= me
.getView();
498 let selection
= view
.getSelection();
499 if (selection
.length
< 1) return;
501 Ext
.create('PVE.node.ACMEDomainEdit', {
502 nodename
: view
.nodename
,
503 nodeconfig
: view
.nodeconfig
,
504 domain
: selection
[0].data
,
505 apiCallDone: function() {
511 removeDomain: function() {
513 let view
= me
.getView();
514 let selection
= view
.getSelection();
515 if (selection
.length
< 1) return;
517 let rec
= selection
[0].data
;
519 if (rec
.configkey
!== 'acme') {
520 params
.delete = rec
.configkey
;
522 let acme
= PVE
.Parser
.parseACME(view
.nodeconfig
.acme
);
523 PVE
.Utils
.remove_domain_from_acme(acme
, rec
.domain
);
524 params
.acme
= PVE
.Parser
.printACME(acme
);
527 Proxmox
.Utils
.API2Request({
529 url
: `/nodes/${view.nodename}/config`,
531 success: function(response
, opt
) {
534 failure: function(response
, opt
) {
535 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
540 toggleEditAccount: function() {
542 let vm
= me
.getViewModel();
543 let editable
= vm
.get('accountEditable');
545 me
.changeAccount(vm
.get('account'), function() {
546 vm
.set('accountEditable', false);
550 vm
.set('accountEditable', true);
554 changeAccount: function(account
, callback
) {
556 let view
= me
.getView();
559 let acme
= PVE
.Parser
.parseACME(view
.nodeconfig
.acme
);
560 acme
.account
= account
;
561 params
.acme
= PVE
.Parser
.printACME(acme
);
563 Proxmox
.Utils
.API2Request({
566 url
: `/nodes/${view.nodename}/config`,
568 success: function(response
, opt
) {
569 if (Ext
.isFunction(callback
)) {
573 failure: function(response
, opt
) {
574 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
581 let view
= me
.getView();
583 Proxmox
.Utils
.API2Request({
588 url
: `/nodes/${view.nodename}/certificates/acme/certificate`,
589 success: function(response
, opt
) {
590 Ext
.create('Proxmox.window.TaskViewer', {
591 upid
: response
.result
.data
,
592 taskDone: function(success
) {
593 me
.orderFinished(success
);
597 failure: function(response
, opt
) {
598 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
603 orderFinished: function(success
) {
604 if (!success
) return;
605 // reload only if the Web UI is open on the same node that the cert was ordered for
606 if (this.getView().nodename
!== Proxmox
.NodeName
) {
609 var txt
= gettext('pveproxy will be restarted with new certificates, please reload the GUI!');
610 Ext
.getBody().mask(txt
, ['pve-static-mask']);
611 // reload after 10 seconds automatically
612 Ext
.defer(function() {
613 window
.location
.reload(true);
619 let view
= me
.getView();
623 addAccount: function() {
625 Ext
.create('PVE.node.ACMEAccountCreate', {
627 taskDone: function() {
629 let accountSelector
= me
.lookup('accountselector');
630 me
.autoChangeAccount
= true;
631 accountSelector
.store
.load();
639 xtype
: 'proxmoxButton',
640 text
: gettext('Add'),
641 handler
: 'addDomain',
645 xtype
: 'proxmoxButton',
646 text
: gettext('Edit'),
648 handler
: 'editDomain',
651 xtype
: 'proxmoxStdRemoveButton',
652 handler
: 'removeDomain',
658 text
: gettext('Order Certificates Now'),
660 disabled
: '{!canOrder}',
666 xtype
: 'displayfield',
667 value
: gettext('Using Account') + ':',
669 hidden
: '{!accountsAvailable}',
673 xtype
: 'displayfield',
674 reference
: 'accounttext',
675 renderer
: (val
) => val
|| Proxmox
.Utils
.NoneText
,
678 hidden
: '{accountTextHidden}',
682 xtype
: 'pveACMEAccountSelector',
684 reference
: 'accountselector',
687 hidden
: '{accountValueHidden}',
692 iconCls
: 'fa black fa-pencil',
694 iconCls
: '{editBtnIcon}',
695 text
: '{editBtnText}',
696 hidden
: '{!accountsAvailable}',
698 handler
: 'toggleEditAccount',
701 xtype
: 'displayfield',
702 value
: gettext('No Account available.'),
704 hidden
: '{accountsAvailable}',
710 reference
: 'accountlink',
711 text
: gettext('Add ACME Account'),
713 hidden
: '{accountsAvailable}',
715 handler
: 'addAccount',
719 updateStore: function(store
, records
, success
) {
723 if (success
&& records
.length
> 0) {
731 me
.nodeconfig
= rec
.data
; // save nodeconfig for updates
733 let account
= 'default';
736 let obj
= PVE
.Parser
.parseACME(rec
.data
.acme
);
737 (obj
.domains
|| []).forEach(domain
=> {
738 if (domain
=== '') return;
748 account
= obj
.account
;
752 let vm
= me
.getViewModel();
753 let oldaccount
= vm
.get('account');
755 // account changed, and we do not edit currently, load again to verify
756 if (oldaccount
!== account
&& !vm
.get('accountEditable')) {
757 vm
.set('configaccount', account
);
758 me
.lookup('accountselector').store
.load();
761 for (let i
= 0; i
< PVE
.Utils
.acmedomain_count
; i
++) {
762 let acmedomain
= rec
.data
[`acmedomain${i}`];
763 if (!acmedomain
) continue;
765 let record
= PVE
.Parser
.parsePropertyString(acmedomain
, 'domain');
767 record
.configkey
= `acmedomain${i}`;
771 vm
.set('domaincount', data
.length
);
772 me
.store
.loadData(data
, false);
776 itemdblclick
: 'editDomain',
783 text
: gettext('Domain'),
788 text
: gettext('Type'),
793 text
: gettext('Plugin'),
797 initComponent: function() {
801 throw "no nodename given";
804 me
.rstore
= Ext
.create('Proxmox.data.UpdateStore', {
807 storeid
: `pve-node-domains-${me.nodename}`,
810 url
: `/api2/json/nodes/${me.nodename}/config`,
814 me
.store
= Ext
.create('Ext.data.Store', {
815 model
: 'pve-acme-domains',
820 me
.mon(me
.rstore
, 'load', 'updateStore', me
);
821 Proxmox
.Utils
.monStoreErrors(me
, me
.rstore
);
822 me
.on('destroy', me
.rstore
.stopUpdate
, me
.rstore
);