]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/node/ACME.js
ui: node/ACME: fix some eslint errors/warnings
[pve-manager.git] / www / manager6 / node / ACME.js
CommitLineData
488be4c2
DC
1Ext.define('PVE.node.ACMEAccountCreate', {
2 extend: 'Proxmox.window.Edit',
3
4 width: 400,
5 title: gettext('Register Account'),
6 isCreate: true,
7 method: 'POST',
8 submitText: gettext('Register'),
9 url: '/cluster/acme/account',
10 showTaskViewer: true,
11
12 items: [
c0afd5cc
DC
13 {
14 xtype: 'proxmoxtextfield',
15 fieldLabel: gettext('Name'),
16 name: 'name',
17 emptyText: 'default',
18 allowBlank: true,
19 },
488be4c2
DC
20 {
21 xtype: 'proxmoxComboGrid',
22 name: 'directory',
23 allowBlank: false,
24 valueField: 'url',
25 displayField: 'name',
26 fieldLabel: gettext('ACME Directory'),
27 store: {
28 autoLoad: true,
29 fields: ['name', 'url'],
30 idProperty: ['name'],
31 proxy: {
32 type: 'proxmox',
33 url: '/api2/json/cluster/acme/directories'
34 },
35 sorters: {
36 property: 'name',
37 order: 'ASC'
38 }
39 },
40 listConfig: {
41 columns: [
42 {
43 header: gettext('Name'),
44 dataIndex: 'name',
45 flex: 1
46 },
47 {
48 header: gettext('URL'),
49 dataIndex: 'url',
50 flex: 1
51 }
52 ]
53 },
54 listeners: {
55 change: function(combogrid, value) {
56 var me = this;
57 if (!value) {
58 return;
59 }
60
61 var disp = me.up('window').down('#tos_url_display');
62 var field = me.up('window').down('#tos_url');
63 var checkbox = me.up('window').down('#tos_checkbox');
64
65 disp.setValue(gettext('Loading'));
66 field.setValue(undefined);
67 checkbox.setValue(undefined);
68
69 Proxmox.Utils.API2Request({
70 url: '/cluster/acme/tos',
71 method: 'GET',
72 params: {
73 directory: value
74 },
75 success: function(response, opt) {
76 me.up('window').down('#tos_url').setValue(response.result.data);
77 me.up('window').down('#tos_url_display').setValue(response.result.data);
78 },
79 failure: function(response, opt) {
80 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
81 }
82 });
83 }
84 }
85 },
86 {
87 xtype: 'displayfield',
88 itemId: 'tos_url_display',
89 fieldLabel: gettext('Terms of Service'),
90 renderer: PVE.Utils.render_optional_url,
91 name: 'tos_url_display'
92 },
93 {
94 xtype: 'hidden',
95 itemId: 'tos_url',
96 name: 'tos_url'
97 },
98 {
99 xtype: 'proxmoxcheckbox',
100 itemId: 'tos_checkbox',
101 fieldLabel: gettext('Accept TOS'),
102 submitValue: false,
103 validateValue: function(value) {
104 if (value && this.checked) {
105 return true;
106 }
107 return false;
108 }
109 },
110 {
111 xtype: 'textfield',
112 name: 'contact',
113 vtype: 'email',
114 allowBlank: false,
115 fieldLabel: gettext('E-Mail')
116 }
117 ]
118
119});
120
121Ext.define('PVE.node.ACMEAccountView', {
122 extend: 'Proxmox.window.Edit',
123
124 width: 600,
125 fieldDefaults: {
126 labelWidth: 140
127 },
128
129 title: gettext('Account'),
130
131 items: [
132 {
133 xtype: 'displayfield',
134 fieldLabel: gettext('E-Mail'),
135 name: 'email'
136 },
137 {
138 xtype: 'displayfield',
139 fieldLabel: gettext('Created'),
140 name: 'createdAt'
141 },
142 {
143 xtype: 'displayfield',
144 fieldLabel: gettext('Status'),
145 name: 'status'
146 },
147 {
148 xtype: 'displayfield',
149 fieldLabel: gettext('Directory'),
150 renderer: PVE.Utils.render_optional_url,
151 name: 'directory'
152 },
153 {
154 xtype: 'displayfield',
155 fieldLabel: gettext('Terms of Services'),
156 renderer: PVE.Utils.render_optional_url,
157 name: 'tos'
158 }
159 ],
160
161 initComponent: function() {
162 var me = this;
163
164 if (!me.accountname) {
165 throw "no account name defined";
166 }
167
168 me.url = '/cluster/acme/account/' + me.accountname;
169
170 me.callParent();
171
172 // hide OK/Reset button, because we just want to show data
173 me.down('toolbar[dock=bottom]').setVisible(false);
174
175 me.load({
176 success: function(response) {
177 var data = response.result.data;
178 data.email = data.account.contact[0];
179 data.createdAt = data.account.createdAt;
180 data.status = data.account.status;
181 me.setValues(data);
182 }
183 });
184 }
185});
186
8e49a93f
DC
187Ext.define('PVE.node.ACMEDomainEdit', {
188 extend: 'Proxmox.window.Edit',
189 alias: 'widget.pveACMEDomainEdit',
190
191 subject: gettext('Domain'),
192 isCreate: false,
3c6b4c80 193 width: 450,
eff602d8 194 onlineHelp: 'sysadmin_certificate_management',
8e49a93f
DC
195
196 items: [
197 {
198 xtype: 'inputpanel',
199 onGetValues: function(values) {
200 let me = this;
201 let win = me.up('pveACMEDomainEdit');
202 let nodeconfig = win.nodeconfig;
203 let olddomain = win.domain || {};
204
205 let params = {
206 digest: nodeconfig.digest,
207 };
208
209 let configkey = olddomain.configkey;
6ac64c3a 210 let acmeObj = PVE.Parser.parseACME(nodeconfig.acme);
8e49a93f
DC
211
212 if (values.type === 'dns') {
213 if (!olddomain.configkey || olddomain.configkey === 'acme') {
214 // look for first free slot
215 for (let i = 0; i < PVE.Utils.acmedomain_count; i++) {
216 if (nodeconfig[`acmedomain${i}`] === undefined) {
217 configkey = `acmedomain${i}`;
218 break;
219 }
220 }
221 if (olddomain.domain) {
222 // we have to remove the domain from the acme domainlist
223 PVE.Utils.remove_domain_from_acme(acmeObj, olddomain.domain);
224 params.acme = PVE.Parser.printACME(acmeObj);
225 }
226 }
227
228 delete values.type;
229 params[configkey] = PVE.Parser.printPropertyString(values, 'domain');
230 } else {
231 if (olddomain.configkey && olddomain.configkey !== 'acme') {
232 // delete the old dns entry
233 params.delete = [olddomain.configkey];
234 }
235
236 // add new, remove old and make entries unique
237 PVE.Utils.add_domain_to_acme(acmeObj, values.domain);
238 PVE.Utils.remove_domain_from_acme(acmeObj, olddomain.domain);
239 params.acme = PVE.Parser.printACME(acmeObj);
240 }
241
242 return params;
243 },
244 items: [
245 {
246 xtype: 'proxmoxKVComboBox',
247 name: 'type',
3c6b4c80 248 fieldLabel: gettext('Challenge Type'),
8e49a93f 249 allowBlank: false,
3c6b4c80 250 value: 'standalone',
8e49a93f 251 comboItems: [
9c164224 252 ['standalone', 'HTTP'],
8e49a93f
DC
253 ['dns', 'DNS'],
254 ],
255 validator: function(value) {
256 let me = this;
257 let win = me.up('pveACMEDomainEdit');
258 let oldconfigkey = win.domain ? win.domain.configkey : undefined;
259 let val = me.getValue();
260 if (val === 'dns' && (!oldconfigkey || oldconfigkey === 'acme')) {
261 // we have to check if there is a 'acmedomain' slot left
262 let found = false;
263 for (let i = 0; i < PVE.Utils.acmedomain_count; i++) {
264 if (!win.nodeconfig[`acmedomain${i}`]) {
265 found = true;
266 }
267 }
268 if (!found) {
269 return gettext('Only 5 Domains with type DNS can be configured');
270 }
271 }
272
273 return true;
274 },
275 listeners: {
276 change: function(cb, value) {
277 let me = this;
278 let view = me.up('pveACMEDomainEdit');
a94b71fb
TL
279 let pluginField = view.down('field[name=plugin]');
280 pluginField.setDisabled(value !== 'dns');
281 pluginField.setHidden(value !== 'dns');
8e49a93f
DC
282 },
283 },
284 },
285 {
286 xtype: 'hidden',
287 name: 'alias',
288 },
8e49a93f
DC
289 {
290 xtype: 'pveACMEPluginSelector',
291 name: 'plugin',
292 disabled: true,
a94b71fb 293 hidden: true,
8e49a93f
DC
294 allowBlank: false,
295 },
a94b71fb
TL
296 {
297 xtype: 'proxmoxtextfield',
298 name: 'domain',
299 allowBlank: false,
300 vtype: 'DnsName',
301 value: '',
302 fieldLabel: gettext('Domain'),
303 },
8e49a93f
DC
304 ],
305 },
306 ],
307
308 initComponent: function() {
309 let me = this;
310
311 if (!me.nodename) {
312 throw 'no nodename given';
313 }
314
315 if (!me.nodeconfig) {
316 throw 'no nodeconfig given';
317 }
318
319 me.isCreate = !me.domain;
8b779b4a
TL
320 if (me.isCreate) {
321 me.domain = `${me.nodename}.`; // TODO: FQDN of node
322 }
8e49a93f
DC
323
324 me.url = `/api2/extjs/nodes/${me.nodename}/config`;
325
326 me.callParent();
327
328 if (!me.isCreate) {
329 me.setValues(me.domain);
8b779b4a
TL
330 } else {
331 me.setValues({ domain: me.domain });
8e49a93f
DC
332 }
333 },
334});
335
fd254233
DC
336Ext.define('pve-acme-domains', {
337 extend: 'Ext.data.Model',
338 fields: ['domain', 'type', 'alias', 'plugin', 'configkey'],
339 idProperty: 'domain',
340});
341
488be4c2 342Ext.define('PVE.node.ACME', {
fd254233
DC
343 extend: 'Ext.grid.Panel',
344 alias: 'widget.pveACMEView',
488be4c2
DC
345
346 margin: '10 0 0 0',
347 title: 'ACME',
348
e666f688
DC
349 emptyText: gettext('No Domains configured'),
350
fd254233
DC
351 viewModel: {
352 data: {
a8b6a80a 353 account: undefined,
fd254233 354 accountEditable: false,
a8b6a80a 355 accountsAvailable: false,
fd254233
DC
356 },
357
358 formulas: {
80bd3209 359 editBtnIcon: (get) => 'fa black fa-' + (get('accountEditable') ? 'check' : 'pencil'),
a8b6a80a
TL
360 accountTextHidden: (get) => get('accountEditable') || !get('accountsAvailable'),
361 accountValueHidden: (get) => !get('accountEditable') || !get('accountsAvailable'),
fd254233
DC
362 },
363 },
364
365 controller: {
366 xclass: 'Ext.app.ViewController',
367
a8b6a80a 368 init: function(view) {
a8b6a80a
TL
369 let accountSelector = this.lookup('accountselector');
370 accountSelector.store.on('load', this.onAccountsLoad, this);
371 },
372
373 onAccountsLoad: function(store, records, success) {
80bd3209
DC
374 let me = this;
375 let vm = me.getViewModel();
a8b6a80a 376 vm.set('accountsAvailable', records.length > 0);
80bd3209
DC
377 if (me.autoChangeAccount && records.length > 0) {
378 me.changeAccount(records[0].data.name, () => {
31c9edc8 379 vm.set('accountEditable', false);
80bd3209 380 me.reload();
31c9edc8
TL
381 });
382 me.autoChangeAccount = false;
383 }
a8b6a80a
TL
384 },
385
fd254233
DC
386 addDomain: function() {
387 let me = this;
388 let view = me.getView();
389
390 Ext.create('PVE.node.ACMEDomainEdit', {
391 nodename: view.nodename,
392 nodeconfig: view.nodeconfig,
393 apiCallDone: function() {
394 me.reload();
395 },
396 }).show();
397 },
398
399 editDomain: function() {
400 let me = this;
401 let view = me.getView();
402
403 let selection = view.getSelection();
404 if (selection.length < 1) return;
405
406 Ext.create('PVE.node.ACMEDomainEdit', {
407 nodename: view.nodename,
408 nodeconfig: view.nodeconfig,
409 domain: selection[0].data,
410 apiCallDone: function() {
411 me.reload();
412 },
413 }).show();
414 },
415
416 removeDomain: function() {
417 let me = this;
418 let view = me.getView();
419 let selection = view.getSelection();
420 if (selection.length < 1) return;
421
422 let rec = selection[0].data;
423 let params = {};
424 if (rec.configkey !== 'acme') {
425 params.delete = rec.configkey;
426 } else {
427 let acme = PVE.Parser.parseACME(view.nodeconfig.acme);
428 PVE.Utils.remove_domain_from_acme(acme, rec.domain);
429 params.acme = PVE.Parser.printACME(acme);
430 }
431
432 Proxmox.Utils.API2Request({
433 method: 'PUT',
434 url: `/nodes/${view.nodename}/config`,
435 params,
436 success: function(response, opt) {
437 me.reload();
438 },
439 failure: function(response, opt) {
440 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
441 },
442 });
443 },
444
445 toggleEditAccount: function() {
446 let me = this;
447 let vm = me.getViewModel();
448 let editable = vm.get('accountEditable');
449 if (editable) {
450 me.changeAccount(vm.get('account'), function() {
451 vm.set('accountEditable', false);
452 me.reload();
453 });
454 } else {
455 vm.set('accountEditable', true);
456 }
457 },
458
459 changeAccount: function(account, callback) {
460 let me = this;
461 let view = me.getView();
462 let params = {};
463
464 let acme = PVE.Parser.parseACME(view.nodeconfig.acme);
465 acme.account = account;
466 params.acme = PVE.Parser.printACME(acme);
467
468 Proxmox.Utils.API2Request({
469 method: 'PUT',
470 waitMsgTarget: view,
471 url: `/nodes/${view.nodename}/config`,
472 params,
473 success: function(response, opt) {
474 if (Ext.isFunction(callback)) {
475 callback();
476 }
477 },
478 failure: function(response, opt) {
479 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
480 },
481 });
482 },
483
484 order: function() {
485 let me = this;
486 let view = me.getView();
487
488 Proxmox.Utils.API2Request({
489 method: 'POST',
490 params: {
491 force: 1,
492 },
493 url: `/nodes/${view.nodename}/certificates/acme/certificate`,
494 success: function(response, opt) {
495 Ext.create('Proxmox.window.TaskViewer', {
496 upid: response.result.data,
497 taskDone: function(success) {
498 me.orderFinished(success);
499 },
500 }).show();
501 },
502 failure: function(response, opt) {
503 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
504 },
505 });
506 },
507
508 orderFinished: function(success) {
509 if (!success) return;
510 var txt = gettext('pveproxy will be restarted with new certificates, please reload the GUI!');
511 Ext.getBody().mask(txt, ['pve-static-mask']);
512 // reload after 10 seconds automatically
513 Ext.defer(function() {
514 window.location.reload(true);
515 }, 10000);
516 },
517
518 reload: function() {
519 let me = this;
520 let view = me.getView();
521 view.rstore.load();
522 },
523
31c9edc8
TL
524 addAccount: function() {
525 let me = this;
526 Ext.create('PVE.node.ACMEAccountCreate', {
527 autoShow: true,
528 taskDone: function() {
529 me.reload();
530 let accountSelector = me.lookup('accountselector');
531 me.autoChangeAccount = true;
532 accountSelector.store.load();
533 },
534 });
fd254233
DC
535 },
536 },
537
488be4c2
DC
538 tbar: [
539 {
fd254233
DC
540 xtype: 'proxmoxButton',
541 text: gettext('Add'),
542 handler: 'addDomain',
543 selModel: false,
544 },
545 {
546 xtype: 'proxmoxButton',
547 text: gettext('Edit'),
548 disabled: true,
549 handler: 'editDomain',
550 },
551 {
552 xtype: 'proxmoxStdRemoveButton',
553 handler: 'removeDomain',
488be4c2 554 },
fd254233 555 '-',
488be4c2
DC
556 {
557 xtype: 'button',
fd254233 558 reference: 'order',
a8b6a80a 559 text: gettext('Order Certificates Now'),
31c9edc8
TL
560 bind: {
561 disabled: '{!accountsAvailable}',
562 },
fd254233
DC
563 handler: 'order',
564 },
565 '-',
566 {
567 xtype: 'displayfield',
a8b6a80a
TL
568 value: gettext('Using Account') + ':',
569 bind: {
570 hidden: '{!accountsAvailable}',
571 },
fd254233
DC
572 },
573 {
574 xtype: 'displayfield',
575 reference: 'accounttext',
576 bind: {
577 value: '{account}',
a8b6a80a 578 hidden: '{accountTextHidden}',
fd254233
DC
579 },
580 },
581 {
582 xtype: 'pveACMEAccountSelector',
583 hidden: true,
584 reference: 'accountselector',
585 bind: {
586 value: '{account}',
a8b6a80a 587 hidden: '{accountValueHidden}',
fd254233 588 },
488be4c2
DC
589 },
590 {
591 xtype: 'button',
fd254233
DC
592 iconCls: 'fa black fa-pencil',
593 baseCls: 'x-plain',
594 userCls: 'pointer',
595 bind: {
a8b6a80a
TL
596 iconCls: '{editBtnIcon}',
597 hidden: '{!accountsAvailable}',
fd254233
DC
598 },
599 handler: 'toggleEditAccount',
488be4c2 600 },
a8b6a80a
TL
601 {
602 xtype: 'displayfield',
603 value: gettext('No Account available.'),
604 bind: {
605 hidden: '{accountsAvailable}',
606 },
607 },
488be4c2
DC
608 {
609 xtype: 'button',
fd254233
DC
610 hidden: true,
611 reference: 'accountlink',
31c9edc8 612 text: gettext('Add ACME Account'),
a8b6a80a
TL
613 bind: {
614 hidden: '{accountsAvailable}',
615 },
31c9edc8 616 handler: 'addAccount',
80bd3209 617 },
488be4c2
DC
618 ],
619
fd254233
DC
620 updateStore: function(store, records, success) {
621 let me = this;
622 let data = [];
623 let rec;
624 if (success && records.length > 0) {
625 rec = records[0];
626 } else {
627 rec = {
80bd3209 628 data: {},
fd254233 629 };
488be4c2 630 }
488be4c2 631
fd254233 632 me.nodeconfig = rec.data; // save nodeconfig for updates
488be4c2 633
fd254233 634 let account = 'default';
488be4c2 635
fd254233
DC
636 if (rec.data.acme) {
637 let obj = PVE.Parser.parseACME(rec.data.acme);
638 (obj.domains || []).forEach(domain => {
639 if (domain === '') return;
640 let record = {
641 domain,
642 type: 'standalone',
643 configkey: 'acme',
644 };
645 data.push(record);
646 });
488be4c2 647
fd254233
DC
648 if (obj.account) {
649 account = obj.account;
650 }
651 }
488be4c2 652
fd254233
DC
653 let vm = me.getViewModel();
654 let oldaccount = vm.get('account');
655
656 // account changed, and we do not edit currently, load again to verify
657 if (oldaccount !== account && !vm.get('accountEditable')) {
658 Proxmox.Utils.API2Request({
659 url: `/cluster/acme/account/${account}`,
660 waitMsgTarget: me,
661 success: function(response, opt) {
662 vm.set('account', account);
663 },
664 failure: function(response, opt) {
665 vm.set('account', Proxmox.Utils.NoneText);
666 },
667 });
668 }
488be4c2 669
fd254233
DC
670 for (let i = 0; i < PVE.Utils.acmedomain_count; i++) {
671 let acmedomain = rec.data[`acmedomain${i}`];
672 if (!acmedomain) continue;
488be4c2 673
fd254233
DC
674 let record = PVE.Parser.parsePropertyString(acmedomain, 'domain');
675 record.type = 'dns';
676 record.configkey = `acmedomain${i}`;
677 data.push(record);
678 }
679
680 me.store.loadData(data, false);
488be4c2
DC
681 },
682
683 listeners: {
fd254233 684 itemdblclick: 'editDomain',
488be4c2
DC
685 },
686
fd254233
DC
687 columns: [
688 {
689 dataIndex: 'domain',
3c6b4c80 690 flex: 5,
fd254233
DC
691 text: gettext('Domain'),
692 },
693 {
694 dataIndex: 'type',
3c6b4c80 695 flex: 1,
fd254233
DC
696 text: gettext('Type'),
697 },
698 {
699 dataIndex: 'plugin',
3c6b4c80 700 flex: 1,
fd254233
DC
701 text: gettext('Plugin'),
702 },
703 ],
488be4c2
DC
704
705 initComponent: function() {
706 var me = this;
707
708 if (!me.nodename) {
709 throw "no nodename given";
710 }
711
fd254233 712 me.rstore = Ext.create('Proxmox.data.UpdateStore', {
1b90cfc6 713 interval: 10 * 1000,
fd254233
DC
714 autoStart: true,
715 storeid: `pve-node-domains-${me.nodename}`,
716 proxy: {
717 type: 'proxmox',
718 url: `/api2/json/nodes/${me.nodename}/config`,
719 },
720 });
721
722 me.store = Ext.create('Ext.data.Store', {
723 model: 'pve-acme-domains',
724 sorters: 'domain',
725 });
488be4c2
DC
726
727 me.callParent();
fd254233
DC
728 me.mon(me.rstore, 'load', 'updateStore', me);
729 Proxmox.Utils.monStoreErrors(me, me.rstore);
730 },
488be4c2 731});