]>
Commit | Line | Data |
---|---|---|
d944805a SI |
1 | Ext.define('Proxmox.form.PBSEncryptionCheckbox', { |
2 | extend: 'Ext.form.field.Checkbox', | |
3 | xtype: 'pbsEncryptionCheckbox', | |
4 | ||
5 | inputValue: true, | |
6 | ||
7 | viewModel: { | |
8 | data: { | |
9 | value: null, | |
10 | originalValue: null, | |
11 | }, | |
12 | formulas: { | |
13 | blabel: (get) => { | |
14 | let v = get('value'); | |
15 | let original = get('originalValue'); | |
16 | if (!get('isCreate') && original) { | |
17 | if (!v) { | |
18 | return gettext('Warning: Existing encryption key will be deleted!'); | |
19 | } | |
20 | return gettext('Active'); | |
21 | } else { | |
22 | return gettext('Auto-generate a client encryption key, saved privately in /etc/pmg'); | |
23 | } | |
24 | }, | |
25 | }, | |
26 | }, | |
27 | ||
28 | bind: { | |
29 | value: '{value}', | |
30 | boxLabel: '{blabel}', | |
31 | }, | |
32 | resetOriginalValue: function() { | |
33 | let me = this; | |
34 | let vm = me.getViewModel(); | |
35 | vm.set('originalValue', me.value); | |
36 | ||
37 | me.callParent(arguments); | |
38 | }, | |
39 | ||
40 | getSubmitData: function() { | |
41 | let me = this; | |
42 | let val = me.getSubmitValue(); | |
43 | if (!me.isCreate) { | |
44 | if (val === null) { | |
45 | return { 'delete': 'encryption-key' }; | |
46 | } else if (val && !!val !== !!me.originalValue) { | |
47 | return { 'encryption-key': 'autogen' }; | |
48 | } | |
49 | } else if (val) { | |
50 | return { 'encryption-key': 'autogen' }; | |
51 | } | |
52 | return null; | |
53 | }, | |
54 | ||
55 | initComponent: function() { | |
56 | let me = this; | |
57 | me.callParent(); | |
58 | ||
59 | let vm = me.getViewModel(); | |
60 | vm.set('isCreate', me.isCreate); | |
61 | }, | |
62 | }); | |
63 | ||
64 | Ext.define('PMG.PBSInputPanel', { | |
65 | extend: 'Ext.tab.Panel', | |
66 | xtype: 'pmgPBSInputPanel', | |
67 | ||
68 | bodyPadding: 10, | |
69 | remoteId: undefined, | |
70 | ||
71 | initComponent: function() { | |
72 | let me = this; | |
73 | ||
74 | me.items = [ | |
75 | { | |
76 | title: gettext('Backup Server'), | |
77 | xtype: 'inputpanel', | |
78 | reference: 'remoteeditpanel', | |
79 | onGetValues: function(values) { | |
80 | values.disable = values.enable ? 0 : 1; | |
81 | delete values.enable; | |
82 | ||
83 | return values; | |
84 | }, | |
85 | ||
86 | column1: [ | |
87 | { | |
88 | xtype: me.isCreate ? 'textfield' : 'displayfield', | |
89 | name: 'remote', | |
90 | value: me.isCreate ? null : undefined, | |
91 | fieldLabel: gettext('ID'), | |
92 | allowBlank: false, | |
93 | }, | |
94 | { | |
95 | xtype: 'proxmoxtextfield', | |
96 | name: 'server', | |
97 | value: me.isCreate ? null : undefined, | |
98 | vtype: 'DnsOrIp', | |
99 | fieldLabel: gettext('Server'), | |
100 | allowBlank: false, | |
101 | }, | |
102 | { | |
103 | xtype: 'proxmoxtextfield', | |
104 | name: 'datastore', | |
105 | value: me.isCreate ? null : undefined, | |
106 | fieldLabel: 'Datastore', | |
107 | allowBlank: false, | |
108 | }, | |
109 | ], | |
110 | column2: [ | |
111 | { | |
112 | xtype: 'proxmoxtextfield', | |
113 | name: 'username', | |
114 | value: me.isCreate ? null : undefined, | |
115 | emptyText: gettext('Example') + ': admin@pbs', | |
116 | fieldLabel: gettext('Username'), | |
117 | regex: /\S+@\w+/, | |
118 | regexText: gettext('Example') + ': admin@pbs', | |
119 | allowBlank: false, | |
120 | }, | |
121 | { | |
122 | xtype: 'proxmoxtextfield', | |
123 | inputType: 'password', | |
124 | name: 'password', | |
125 | value: me.isCreate ? null : undefined, | |
126 | emptyText: me.isCreate ? gettext('None') : '********', | |
127 | fieldLabel: gettext('Password'), | |
128 | allowBlank: true, | |
129 | }, | |
130 | { | |
131 | xtype: 'proxmoxcheckbox', | |
132 | name: 'enable', | |
133 | checked: true, | |
134 | uncheckedValue: 0, | |
135 | fieldLabel: gettext('Enable'), | |
136 | }, | |
137 | ], | |
138 | columnB: [ | |
139 | { | |
140 | xtype: 'proxmoxtextfield', | |
141 | name: 'fingerprint', | |
142 | value: me.isCreate ? null : undefined, | |
143 | fieldLabel: gettext('Fingerprint'), | |
144 | emptyText: gettext('Server certificate SHA-256 fingerprint, required for self-signed certificates'), | |
145 | regex: /[A-Fa-f0-9]{2}(:[A-Fa-f0-9]{2}){31}/, | |
146 | regexText: gettext('Example') + ': AB:CD:EF:...', | |
147 | allowBlank: true, | |
148 | }, | |
149 | { | |
150 | xtype: 'pbsEncryptionCheckbox', | |
151 | name: 'encryption-key', | |
152 | isCreate: me.isCreate, | |
153 | fieldLabel: gettext('Encryption Key'), | |
154 | }, | |
155 | { | |
156 | xtype: 'displayfield', | |
157 | userCls: 'pmx-hint', | |
158 | value: `Proxmox Backup Server is currently in beta.`, | |
159 | }, | |
160 | ], | |
161 | }, | |
162 | { | |
163 | title: gettext('Prune Options'), | |
164 | xtype: 'inputpanel', | |
165 | reference: 'prunepanel', | |
166 | column1: [ | |
167 | { | |
168 | xtype: 'proxmoxintegerfield', | |
169 | fieldLabel: gettext('Keep Last'), | |
170 | name: 'keep-last', | |
171 | cbind: { | |
172 | deleteEmpty: '{!isCreate}', | |
173 | }, | |
174 | minValue: 1, | |
175 | allowBlank: true, | |
176 | }, | |
177 | { | |
178 | xtype: 'proxmoxintegerfield', | |
179 | fieldLabel: gettext('Keep Daily'), | |
180 | name: 'keep-daily', | |
181 | cbind: { | |
182 | deleteEmpty: '{!isCreate}', | |
183 | }, | |
184 | minValue: 1, | |
185 | allowBlank: true, | |
186 | }, | |
187 | { | |
188 | xtype: 'proxmoxintegerfield', | |
189 | fieldLabel: gettext('Keep Monthly'), | |
190 | name: 'keep-monthly', | |
191 | cbind: { | |
192 | deleteEmpty: '{!isCreate}', | |
193 | }, | |
194 | minValue: 1, | |
195 | allowBlank: true, | |
196 | }, | |
197 | ], | |
198 | column2: [ | |
199 | { | |
200 | xtype: 'proxmoxintegerfield', | |
201 | fieldLabel: gettext('Keep Hourly'), | |
202 | name: 'keep-hourly', | |
203 | cbind: { | |
204 | deleteEmpty: '{!isCreate}', | |
205 | }, | |
206 | minValue: 1, | |
207 | allowBlank: true, | |
208 | }, | |
209 | { | |
210 | xtype: 'proxmoxintegerfield', | |
211 | fieldLabel: gettext('Keep Weekly'), | |
212 | name: 'keep-weekly', | |
213 | cbind: { | |
214 | deleteEmpty: '{!isCreate}', | |
215 | }, | |
216 | minValue: 1, | |
217 | allowBlank: true, | |
218 | }, | |
219 | { | |
220 | xtype: 'proxmoxintegerfield', | |
221 | fieldLabel: gettext('Keep Yearly'), | |
222 | name: 'keep-yearly', | |
223 | cbind: { | |
224 | deleteEmpty: '{!isCreate}', | |
225 | }, | |
226 | minValue: 1, | |
227 | allowBlank: true, | |
228 | }, | |
229 | ], | |
230 | }, | |
231 | ]; | |
232 | ||
233 | me.callParent(); | |
234 | }, | |
235 | ||
236 | }); | |
237 | ||
238 | Ext.define('PMG.PBSEdit', { | |
239 | extend: 'Proxmox.window.Edit', | |
240 | xtype: 'pmgPBSEdit', | |
241 | ||
242 | subject: 'Proxmox Backup Server', | |
243 | isAdd: true, | |
244 | ||
245 | bodyPadding: 0, | |
246 | ||
247 | initComponent: function() { | |
248 | let me = this; | |
249 | ||
250 | me.isCreate = !me.remoteId; | |
251 | ||
252 | if (me.isCreate) { | |
253 | me.url = '/api2/extjs/config/pbs'; | |
254 | me.method = 'POST'; | |
255 | } else { | |
256 | me.url = '/api2/extjs/config/pbs/' + me.remoteId; | |
257 | me.method = 'PUT'; | |
258 | } | |
259 | ||
260 | let ipanel = Ext.create('PMG.PBSInputPanel', { | |
261 | isCreate: me.isCreate, | |
262 | remoteId: me.remoteId, | |
263 | }); | |
264 | ||
265 | me.items = [ipanel]; | |
266 | ||
267 | me.fieldDefaults = { | |
268 | labelWidth: 150, | |
269 | }; | |
270 | ||
271 | me.callParent(); | |
272 | ||
273 | if (!me.isCreate) { | |
274 | me.load({ | |
275 | success: function(response, options) { | |
276 | let values = response.result.data; | |
277 | ||
278 | values.enable = values.disable ? 0 : 1; | |
279 | me.down('inputpanel[reference=remoteeditpanel]').setValues(values); | |
280 | me.down('inputpanel[reference=prunepanel]').setValues(values); | |
281 | }, | |
282 | }); | |
283 | } | |
284 | }, | |
285 | }); | |
286 | ||
287 | Ext.define('PMG.PBSScheduleEdit', { | |
288 | extend: 'Proxmox.window.Edit', | |
289 | xtype: 'pmgPBSScheduleEdit', | |
290 | ||
291 | isAdd: true, | |
292 | method: 'POST', | |
293 | subject: gettext('Scheduled Backup'), | |
294 | autoLoad: true, | |
295 | items: [ | |
296 | { | |
297 | xtype: 'proxmoxKVComboBox', | |
298 | name: 'schedule', | |
299 | fieldLabel: gettext('Schedule'), | |
300 | comboItems: [ | |
301 | ['daily', 'daily'], | |
302 | ['hourly', 'hourly'], | |
303 | ['weekly', 'weekly'], | |
304 | ['monthly', 'monthly'], | |
305 | ], | |
306 | editable: true, | |
307 | emptyText: 'Systemd Calender Event', | |
308 | }, | |
309 | { | |
310 | xtype: 'proxmoxKVComboBox', | |
311 | name: 'delay', | |
312 | fieldLabel: gettext('Random Delay'), | |
313 | comboItems: [ | |
314 | ['0s', 'no delay'], | |
315 | ['15 minutes', '15 Minutes'], | |
316 | ['6 hours', '6 hours'], | |
317 | ], | |
318 | editable: true, | |
319 | emptyText: 'Systemd TimeSpan', | |
320 | }, | |
321 | ], | |
322 | initComponent: function() { | |
323 | let me = this; | |
324 | ||
325 | me.url = '/nodes/' + Proxmox.NodeName + '/pbs/' + me.remote + '/timer'; | |
326 | me.callParent(); | |
327 | }, | |
328 | }); | |
329 | ||
330 | Ext.define('PMG.PBSConfig', { | |
331 | extend: 'Ext.panel.Panel', | |
332 | xtype: 'pmgPBSConfig', | |
333 | ||
334 | controller: { | |
335 | xclass: 'Ext.app.ViewController', | |
336 | ||
337 | callRestore: function(grid, record) { | |
338 | let name = this.getViewModel().get('name'); | |
339 | Ext.create('PMG.RestoreWindow', { | |
340 | name: name, | |
341 | backup_time: record.data.time, | |
342 | }).show(); | |
343 | }, | |
344 | ||
345 | restoreSnapshot: function(button) { | |
346 | let me = this; | |
347 | let view = me.lookup('pbsremotegrid'); | |
348 | let record = view.getSelection()[0]; | |
349 | me.callRestore(view, record); | |
350 | }, | |
351 | ||
352 | runBackup: function(button) { | |
353 | let me = this; | |
354 | let view = me.lookup('pbsremotegrid'); | |
355 | let name = me.getViewModel().get('name'); | |
356 | Proxmox.Utils.API2Request({ | |
357 | url: "/nodes/" + Proxmox.NodeName + "/pbs/" + name + "/backup", | |
358 | method: 'POST', | |
359 | waitMsgTarget: view, | |
360 | failure: function(response, opts) { | |
361 | Ext.Msg.alert(gettext('Error'), response.htmlStatus); | |
362 | }, | |
363 | success: function(response, opts) { | |
364 | let upid = response.result.data; | |
365 | ||
366 | let win = Ext.create('Proxmox.window.TaskViewer', { | |
367 | upid: upid, | |
368 | }); | |
369 | win.show(); | |
370 | me.mon(win, 'close', function() { view.getStore().load(); }); | |
371 | }, | |
372 | }); | |
373 | }, | |
374 | ||
375 | reload: function(grid) { | |
376 | let me = this; | |
377 | let selection = grid.getSelection(); | |
378 | me.showInfo(grid, selection); | |
379 | }, | |
380 | ||
381 | showInfo: function(grid, selected) { | |
382 | let me = this; | |
383 | let viewModel = me.getViewModel(); | |
384 | if (selected[0]) { | |
385 | let name = selected[0].data.remote; | |
386 | viewModel.set('selected', true); | |
387 | viewModel.set('name', name); | |
388 | ||
389 | // set grid stores and load them | |
390 | let remstore = me.lookup('pbsremotegrid').getStore(); | |
391 | remstore.getProxy().setUrl('/api2/json/nodes/' + Proxmox.NodeName + '/pbs/' + name + '/snapshots'); | |
392 | remstore.load(); | |
393 | } else { | |
394 | viewModel.set('selected', false); | |
395 | } | |
396 | }, | |
397 | reloadSnapshots: function() { | |
398 | let me = this; | |
399 | let grid = me.lookup('grid'); | |
400 | let selection = grid.getSelection(); | |
401 | me.showInfo(grid, selection); | |
402 | }, | |
403 | init: function(view) { | |
404 | let me = this; | |
405 | me.lookup('grid').relayEvents(view, ['activate']); | |
406 | let pbsremotegrid = me.lookup('pbsremotegrid'); | |
407 | ||
408 | Proxmox.Utils.monStoreErrors(pbsremotegrid, pbsremotegrid.getStore(), true); | |
409 | }, | |
410 | ||
411 | control: { | |
412 | 'grid[reference=grid]': { | |
413 | selectionchange: 'showInfo', | |
414 | load: 'reload', | |
415 | }, | |
416 | 'grid[reference=pbsremotegrid]': { | |
417 | itemdblclick: 'restoreSnapshot', | |
418 | }, | |
419 | }, | |
420 | }, | |
421 | ||
422 | viewModel: { | |
423 | data: { | |
424 | name: '', | |
425 | selected: false, | |
426 | }, | |
427 | }, | |
428 | ||
429 | layout: 'border', | |
430 | ||
431 | items: [ | |
432 | { | |
433 | region: 'center', | |
434 | reference: 'grid', | |
435 | xtype: 'pmgPBSConfigGrid', | |
436 | border: false, | |
437 | }, | |
438 | { | |
439 | xtype: 'grid', | |
440 | region: 'south', | |
441 | reference: 'pbsremotegrid', | |
442 | hidden: true, | |
443 | height: '70%', | |
444 | border: false, | |
445 | split: true, | |
446 | emptyText: gettext('No backups on remote'), | |
447 | tbar: [ | |
448 | { | |
449 | xtype: 'proxmoxButton', | |
450 | text: gettext('Backup'), | |
451 | handler: 'runBackup', | |
452 | selModel: false, | |
453 | }, | |
454 | { | |
455 | xtype: 'proxmoxButton', | |
456 | text: gettext('Restore'), | |
457 | handler: 'restoreSnapshot', | |
458 | disabled: true, | |
459 | }, | |
460 | { | |
461 | xtype: 'proxmoxStdRemoveButton', | |
462 | text: gettext('Forget Snapshot'), | |
463 | disabled: true, | |
464 | getUrl: function(rec) { | |
465 | let me = this; | |
466 | let remote = me.lookupViewModel().get('name'); | |
467 | return '/nodes/' + Proxmox.NodeName + '/pbs/' + remote +'/snapshots/'+ rec.data.time; | |
468 | }, | |
469 | confirmMsg: function(rec) { | |
470 | let me = this; | |
471 | let time = rec.data.time; | |
472 | return Ext.String.format(gettext('Are you sure you want to forget snapshot {0}'), `'${time}'`); | |
473 | }, | |
474 | callback: 'reloadSnapshots', | |
475 | }, | |
476 | ], | |
477 | store: { | |
478 | fields: ['time', 'size', 'ctime', 'encrypted'], | |
479 | proxy: { type: 'proxmox' }, | |
480 | sorters: [ | |
481 | { | |
482 | property: 'time', | |
483 | direction: 'DESC', | |
484 | }, | |
485 | ], | |
486 | }, | |
487 | bind: { | |
488 | title: Ext.String.format(gettext("Backup snapshots on '{0}'"), '{name}'), | |
489 | hidden: '{!selected}', | |
490 | }, | |
491 | columns: [ | |
492 | { | |
493 | text: 'Time', | |
494 | dataIndex: 'time', | |
495 | flex: 1, | |
496 | }, | |
497 | { | |
498 | text: 'Size', | |
499 | dataIndex: 'size', | |
500 | flex: 1, | |
501 | }, | |
502 | { | |
503 | text: 'Encrypted', | |
504 | dataIndex: 'encrypted', | |
505 | renderer: Proxmox.Utils.format_boolean, | |
506 | flex: 1, | |
507 | }, | |
508 | ], | |
509 | }, | |
510 | ], | |
511 | ||
512 | }); | |
513 | ||
514 | Ext.define('pmg-pbs-config', { | |
515 | extend: 'Ext.data.Model', | |
516 | fields: ['remote', 'server', 'datastore', 'username', 'disabled'], | |
517 | proxy: { | |
518 | type: 'proxmox', | |
519 | url: '/api2/json/config/pbs', | |
520 | }, | |
521 | idProperty: 'remote', | |
522 | }); | |
523 | ||
524 | Ext.define('PMG.PBSConfigGrid', { | |
525 | extend: 'Ext.grid.GridPanel', | |
526 | xtype: 'pmgPBSConfigGrid', | |
527 | ||
528 | controller: { | |
529 | xclass: 'Ext.app.ViewController', | |
530 | ||
531 | run_editor: function() { | |
532 | let me = this; | |
533 | let view = me.getView(); | |
534 | let rec = view.getSelection()[0]; | |
535 | if (!rec) { | |
536 | return; | |
537 | } | |
538 | ||
539 | let win = Ext.createWidget('pmgPBSEdit', { | |
540 | remoteId: rec.data.remote, | |
541 | }); | |
542 | win.on('destroy', me.reload, me); | |
543 | win.load(); | |
544 | win.show(); | |
545 | }, | |
546 | ||
547 | newRemote: function() { | |
548 | let me = this; | |
549 | let win = Ext.createWidget('pmgPBSEdit', {}); | |
550 | win.on('destroy', me.reload, me); | |
551 | win.show(); | |
552 | }, | |
553 | ||
554 | ||
555 | reload: function() { | |
556 | let me = this; | |
557 | let view = me.getView(); | |
558 | view.getStore().load(); | |
559 | view.fireEvent('load', view); | |
560 | }, | |
561 | ||
562 | createSchedule: function() { | |
563 | let me = this; | |
564 | let view = me.getView(); | |
565 | let rec = view.getSelection()[0]; | |
566 | let remotename = rec.data.remote; | |
567 | let win = Ext.createWidget('pmgPBSScheduleEdit', { | |
568 | remote: remotename, | |
569 | }); | |
570 | win.on('destroy', me.reload, me); | |
571 | win.show(); | |
572 | }, | |
573 | ||
574 | init: function(view) { | |
575 | let me = this; | |
576 | Proxmox.Utils.monStoreErrors(view, view.getStore(), true); | |
577 | }, | |
578 | ||
579 | }, | |
580 | ||
581 | store: { | |
582 | model: 'pmg-pbs-config', | |
583 | sorters: [{ | |
584 | property: 'remote', | |
585 | order: 'DESC', | |
586 | }], | |
587 | }, | |
588 | ||
589 | tbar: [ | |
590 | { | |
591 | xtype: 'proxmoxButton', | |
592 | text: gettext('Edit'), | |
593 | disabled: true, | |
594 | handler: 'run_editor', | |
595 | }, | |
596 | { | |
597 | text: gettext('Create'), | |
598 | handler: 'newRemote', | |
599 | }, | |
600 | { | |
601 | xtype: 'proxmoxStdRemoveButton', | |
602 | baseurl: '/config/pbs', | |
603 | callback: 'reload', | |
604 | }, | |
605 | { | |
606 | xtype: 'proxmoxButton', | |
607 | text: gettext('Schedule'), | |
608 | enableFn: function(rec) { | |
609 | return !rec.data.disable; | |
610 | }, | |
611 | disabled: true, | |
612 | handler: 'createSchedule', | |
613 | }, | |
614 | { | |
615 | xtype: 'proxmoxStdRemoveButton', | |
616 | baseurl: '/nodes/' + Proxmox.NodeName + '/pbs/', | |
617 | callback: 'reload', | |
618 | text: gettext('Remove Schedule'), | |
619 | confirmMsg: function(rec) { | |
620 | let me = this; | |
621 | let name = rec.getId(); | |
622 | return Ext.String.format(gettext('Are you sure you want to remove the schedule for {0}'), `'${name}'`); | |
623 | }, | |
624 | getUrl: function(rec) { | |
625 | let me = this; | |
626 | return me.baseurl + '/' + rec.getId() + '/timer'; | |
627 | }, | |
628 | }, | |
629 | ], | |
630 | ||
631 | listeners: { | |
632 | itemdblclick: 'run_editor', | |
633 | activate: 'reload', | |
634 | }, | |
635 | ||
636 | columns: [ | |
637 | { | |
638 | header: gettext('Backup Server name'), | |
639 | sortable: true, | |
640 | dataIndex: 'remote', | |
641 | flex: 2, | |
642 | }, | |
643 | { | |
644 | header: gettext('Server'), | |
645 | sortable: true, | |
646 | dataIndex: 'server', | |
647 | flex: 2, | |
648 | }, | |
649 | { | |
650 | header: gettext('Datastore'), | |
651 | sortable: true, | |
652 | dataIndex: 'datastore', | |
653 | flex: 1, | |
654 | }, | |
655 | { | |
656 | header: gettext('User ID'), | |
657 | sortable: true, | |
658 | dataIndex: 'username', | |
659 | flex: 1, | |
660 | }, | |
661 | { | |
662 | header: gettext('Encryption'), | |
663 | width: 80, | |
664 | sortable: true, | |
665 | dataIndex: 'encryption-key', | |
666 | renderer: Proxmox.Utils.format_boolean, | |
667 | }, | |
668 | { | |
669 | header: gettext('Enabled'), | |
670 | width: 80, | |
671 | sortable: true, | |
672 | dataIndex: 'disable', | |
673 | renderer: Proxmox.Utils.format_neg_boolean, | |
674 | }, | |
675 | ], | |
676 | ||
677 | }); | |
678 |