]> git.proxmox.com Git - proxmox-widget-toolkit.git/blame - src/panel/ACMEDomains.js
edit: fix comment typos
[proxmox-widget-toolkit.git] / src / panel / ACMEDomains.js
CommitLineData
8915422f
WB
1Ext.define('proxmox-acme-domains', {
2 extend: 'Ext.data.Model',
3 fields: ['domain', 'type', 'alias', 'plugin', 'configkey'],
4 idProperty: 'domain',
5});
6
7Ext.define('Proxmox.panel.ACMEDomains', {
8 extend: 'Ext.grid.Panel',
9 xtype: 'pmxACMEDomains',
10 mixins: ['Proxmox.Mixin.CBind'],
11
12 margin: '10 0 0 0',
13 title: 'ACME',
14
15 emptyText: gettext('No Domains configured'),
16
17 // URL to the config containing 'acme' and 'acmedomainX' properties
18 url: undefined,
19
20 // array of { name, url, usageLabel }
21 domainUsages: undefined,
22 // if no domainUsages parameter is supllied, the orderUrl is required instead:
23 orderUrl: undefined,
ef7a8290
WB
24 // Force the use of 'acmedomainX' properties.
25 separateDomainEntries: undefined,
8915422f
WB
26
27 acmeUrl: undefined,
28
29 cbindData: function(config) {
30 let me = this;
31 return {
32 acmeUrl: me.acmeUrl,
33 accountUrl: `/api2/json/${me.acmeUrl}/account`,
34 };
35 },
36
37 viewModel: {
38 data: {
39 domaincount: 0,
40 account: undefined, // the account we display
41 configaccount: undefined, // the account set in the config
42 accountEditable: false,
43 accountsAvailable: false,
44 hasUsage: false,
45 },
46
47 formulas: {
48 canOrder: (get) => !!get('account') && get('domaincount') > 0,
49 editBtnIcon: (get) => 'fa black fa-' + (get('accountEditable') ? 'check' : 'pencil'),
50 accountTextHidden: (get) => get('accountEditable') || !get('accountsAvailable'),
51 accountValueHidden: (get) => !get('accountEditable') || !get('accountsAvailable'),
8915422f
WB
52 },
53 },
54
55 controller: {
56 xclass: 'Ext.app.ViewController',
57
58 init: function(view) {
59 let accountSelector = this.lookup('accountselector');
60 accountSelector.store.on('load', this.onAccountsLoad, this);
61 },
62
63 onAccountsLoad: function(store, records, success) {
64 let me = this;
65 let vm = me.getViewModel();
66 let configaccount = vm.get('configaccount');
67 vm.set('accountsAvailable', records.length > 0);
68 if (me.autoChangeAccount && records.length > 0) {
69 me.changeAccount(records[0].data.name, () => {
70 vm.set('accountEditable', false);
71 me.reload();
72 });
73 me.autoChangeAccount = false;
74 } else if (configaccount) {
75 if (store.findExact('name', configaccount) !== -1) {
76 vm.set('account', configaccount);
77 } else {
78 vm.set('account', null);
79 }
80 }
81 },
82
83 addDomain: function() {
84 let me = this;
85 let view = me.getView();
86
87 Ext.create('Proxmox.window.ACMEDomainEdit', {
88 url: view.url,
89 acmeUrl: view.acmeUrl,
90 nodeconfig: view.nodeconfig,
91 domainUsages: view.domainUsages,
ef7a8290 92 separateDomainEntries: view.separateDomainEntries,
8915422f
WB
93 apiCallDone: function() {
94 me.reload();
95 },
96 }).show();
97 },
98
99 editDomain: function() {
100 let me = this;
101 let view = me.getView();
102
103 let selection = view.getSelection();
104 if (selection.length < 1) return;
105
106 Ext.create('Proxmox.window.ACMEDomainEdit', {
107 url: view.url,
108 acmeUrl: view.acmeUrl,
109 nodeconfig: view.nodeconfig,
110 domainUsages: view.domainUsages,
ef7a8290 111 separateDomainEntries: view.separateDomainEntries,
8915422f
WB
112 domain: selection[0].data,
113 apiCallDone: function() {
114 me.reload();
115 },
116 }).show();
117 },
118
119 removeDomain: function() {
120 let me = this;
121 let view = me.getView();
122 let selection = view.getSelection();
123 if (selection.length < 1) return;
124
125 let rec = selection[0].data;
126 let params = {};
127 if (rec.configkey !== 'acme') {
128 params.delete = rec.configkey;
129 } else {
130 let acme = Proxmox.Utils.parseACME(view.nodeconfig.acme);
131 Proxmox.Utils.remove_domain_from_acme(acme, rec.domain);
132 params.acme = Proxmox.Utils.printACME(acme);
133 }
134
135 Proxmox.Utils.API2Request({
136 method: 'PUT',
137 url: view.url,
138 params,
139 success: function(response, opt) {
140 me.reload();
141 },
142 failure: function(response, opt) {
143 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
144 },
145 });
146 },
147
148 toggleEditAccount: function() {
149 let me = this;
150 let vm = me.getViewModel();
151 let editable = vm.get('accountEditable');
152 if (editable) {
153 me.changeAccount(vm.get('account'), function() {
154 vm.set('accountEditable', false);
155 me.reload();
156 });
157 } else {
158 vm.set('accountEditable', true);
159 }
160 },
161
162 changeAccount: function(account, callback) {
163 let me = this;
164 let view = me.getView();
165 let params = {};
166
167 let acme = Proxmox.Utils.parseACME(view.nodeconfig.acme);
168 acme.account = account;
169 params.acme = Proxmox.Utils.printACME(acme);
170
171 Proxmox.Utils.API2Request({
172 method: 'PUT',
173 waitMsgTarget: view,
174 url: view.url,
175 params,
176 success: function(response, opt) {
177 if (Ext.isFunction(callback)) {
178 callback();
179 }
180 },
181 failure: function(response, opt) {
182 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
183 },
184 });
185 },
186
187 order: function(cert) {
188 let me = this;
189 let view = me.getView();
190
191 Proxmox.Utils.API2Request({
192 method: 'POST',
193 params: {
194 force: 1,
195 },
196 url: cert ? cert.url : view.orderUrl,
197 success: function(response, opt) {
198 Ext.create('Proxmox.window.TaskViewer', {
199 upid: response.result.data,
200 taskDone: function(success) {
201 me.orderFinished(success, cert);
202 },
203 }).show();
204 },
205 failure: function(response, opt) {
206 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
207 },
208 });
209 },
210
211 orderFinished: function(success, cert) {
212 if (!success || !cert.reloadUi) return;
c5cade50
TL
213
214 Ext.getBody().mask(
215 gettext('API server will be restarted to use new certificates, please reload web-interface!'),
216 ['pve-static-mask'],
217 );
218 // try to reload after 10 seconds automatically
219 Ext.defer(() => window.location.reload(true), 10000);
8915422f
WB
220 },
221
222 reload: function() {
223 let me = this;
224 let view = me.getView();
225 view.rstore.load();
226 },
227
228 addAccount: function() {
229 let me = this;
230 let view = me.getView();
231 Ext.create('Proxmox.window.ACMEAccountCreate', {
232 autoShow: true,
233 acmeUrl: view.acmeUrl,
234 taskDone: function() {
235 me.reload();
236 let accountSelector = me.lookup('accountselector');
237 me.autoChangeAccount = true;
238 accountSelector.store.load();
239 },
240 });
241 },
242 },
243
244 tbar: [
245 {
246 xtype: 'proxmoxButton',
247 text: gettext('Add'),
248 handler: 'addDomain',
249 selModel: false,
250 },
251 {
252 xtype: 'proxmoxButton',
253 text: gettext('Edit'),
254 disabled: true,
255 handler: 'editDomain',
256 },
257 {
258 xtype: 'proxmoxStdRemoveButton',
259 handler: 'removeDomain',
260 },
261 '-',
262 'order-menu', // placeholder, filled in initComponent
263 '-',
264 {
265 xtype: 'displayfield',
266 value: gettext('Using Account') + ':',
267 bind: {
268 hidden: '{!accountsAvailable}',
269 },
270 },
271 {
272 xtype: 'displayfield',
273 reference: 'accounttext',
274 renderer: (val) => val || Proxmox.Utils.NoneText,
275 bind: {
276 value: '{account}',
277 hidden: '{accountTextHidden}',
278 },
279 },
280 {
281 xtype: 'pmxACMEAccountSelector',
282 hidden: true,
283 reference: 'accountselector',
284 cbind: {
285 url: '{accountUrl}',
286 },
287 bind: {
288 value: '{account}',
289 hidden: '{accountValueHidden}',
290 },
291 },
292 {
293 xtype: 'button',
294 iconCls: 'fa black fa-pencil',
295 baseCls: 'x-plain',
296 userCls: 'pointer',
297 bind: {
298 iconCls: '{editBtnIcon}',
299 hidden: '{!accountsAvailable}',
300 },
301 handler: 'toggleEditAccount',
302 },
303 {
304 xtype: 'displayfield',
305 value: gettext('No Account available.'),
306 bind: {
307 hidden: '{accountsAvailable}',
308 },
309 },
310 {
311 xtype: 'button',
312 hidden: true,
313 reference: 'accountlink',
314 text: gettext('Add ACME Account'),
315 bind: {
316 hidden: '{accountsAvailable}',
317 },
318 handler: 'addAccount',
319 },
320 ],
321
322 updateStore: function(store, records, success) {
323 let me = this;
324 let data = [];
325 let rec;
326 if (success && records.length > 0) {
327 rec = records[0];
328 } else {
329 rec = {
330 data: {},
331 };
332 }
333
334 me.nodeconfig = rec.data; // save nodeconfig for updates
335
336 let account = 'default';
337
338 if (rec.data.acme) {
339 let obj = Proxmox.Utils.parseACME(rec.data.acme);
340 (obj.domains || []).forEach(domain => {
341 if (domain === '') return;
342 let record = {
343 domain,
344 type: 'standalone',
345 configkey: 'acme',
346 };
347 data.push(record);
348 });
349
350 if (obj.account) {
351 account = obj.account;
352 }
353 }
354
355 let vm = me.getViewModel();
356 let oldaccount = vm.get('account');
357
358 // account changed, and we do not edit currently, load again to verify
359 if (oldaccount !== account && !vm.get('accountEditable')) {
360 vm.set('configaccount', account);
361 me.lookup('accountselector').store.load();
362 }
363
364 for (let i = 0; i < Proxmox.Utils.acmedomain_count; i++) {
365 let acmedomain = rec.data[`acmedomain${i}`];
366 if (!acmedomain) continue;
367
368 let record = Proxmox.Utils.parsePropertyString(acmedomain, 'domain');
369 record.type = record.plugin ? 'dns' : 'standalone';
370 record.configkey = `acmedomain${i}`;
371 data.push(record);
372 }
373
374 vm.set('domaincount', data.length);
375 me.store.loadData(data, false);
376 },
377
378 listeners: {
379 itemdblclick: 'editDomain',
380 },
381
382 columns: [
383 {
384 dataIndex: 'domain',
385 flex: 5,
386 text: gettext('Domain'),
387 },
388 {
389 dataIndex: 'usage',
390 flex: 1,
391 text: gettext('Usage'),
392 bind: {
393 hidden: '{!hasUsage}',
394 },
395 },
396 {
397 dataIndex: 'type',
398 flex: 1,
399 text: gettext('Type'),
400 },
401 {
402 dataIndex: 'plugin',
403 flex: 1,
404 text: gettext('Plugin'),
405 },
406 ],
407
408 initComponent: function() {
409 let me = this;
410
411 if (!me.acmeUrl) {
412 throw "no acmeUrl given";
413 }
414
415 if (!me.url) {
416 throw "no url given";
417 }
418
419 if (!me.nodename) {
420 throw "no nodename given";
421 }
422
423 if (!me.domainUsages && !me.orderUrl) {
424 throw "neither domainUsages nor orderUrl given";
425 }
426
427 me.rstore = Ext.create('Proxmox.data.UpdateStore', {
428 interval: 10 * 1000,
429 autoStart: true,
430 storeid: `proxmox-node-domains-${me.nodename}`,
431 proxy: {
432 type: 'proxmox',
433 url: `/api2/json/${me.url}`,
434 },
435 });
436
437 me.store = Ext.create('Ext.data.Store', {
438 model: 'proxmox-acme-domains',
439 sorters: 'domain',
440 });
441
442 if (me.domainUsages) {
443 let items = [];
444
445 for (const cert of me.domainUsages) {
446 if (!cert.name) {
447 throw "missing certificate url";
448 }
449
450 if (!cert.url) {
451 throw "missing certificate url";
452 }
453
454 items.push({
455 text: Ext.String.format('Order {0} Certificate Now', cert.name),
456 handler: function() {
457 return me.getController().order(cert);
458 },
459 });
460 }
461 me.tbar.splice(
462 me.tbar.indexOf("order-menu"),
463 1,
464 {
465 text: gettext('Order Certificates Now'),
466 menu: {
467 xtype: 'menu',
468 items,
469 },
470 },
471 );
472 } else {
473 me.tbar.splice(
474 me.tbar.indexOf("order-menu"),
475 1,
476 {
477 xtype: 'button',
478 reference: 'order',
479 text: gettext('Order Certificates Now'),
480 bind: {
481 disabled: '{!canOrder}',
482 },
483 handler: function() {
484 return me.getController().order();
485 },
486 },
487 );
488 }
489
490 me.callParent();
491 me.getViewModel().set('hasUsage', !!me.domainUsages);
492 me.mon(me.rstore, 'load', 'updateStore', me);
493 Proxmox.Utils.monStoreErrors(me, me.rstore);
494 me.on('destroy', me.rstore.stopUpdate, me.rstore);
495 },
496});