]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/dc/Backup.js
ui: backup detail: move search bar in panel header to save vspace
[pve-manager.git] / www / manager6 / dc / Backup.js
CommitLineData
ad74d86c 1Ext.define('PVE.dc.BackupEdit', {
9fccc702 2 extend: 'Proxmox.window.Edit',
ad74d86c
DM
3 alias: ['widget.pveDcBackupEdit'],
4
fc03fcb8
TL
5 defaultFocus: undefined,
6
ad74d86c 7 initComponent : function() {
ad74d86c
DM
8 var me = this;
9
d5e771ce 10 me.isCreate = !me.jobid;
ad74d86c
DM
11
12 var url;
13 var method;
14
d5e771ce 15 if (me.isCreate) {
ad74d86c
DM
16 url = '/api2/extjs/cluster/backup';
17 method = 'POST';
18 } else {
19 url = '/api2/extjs/cluster/backup/' + me.jobid;
20 method = 'PUT';
21 }
22
23 var vmidField = Ext.create('Ext.form.field.Hidden', {
24 name: 'vmid'
25 });
26
d5e771ce 27 // 'value' can be assigned a string or an array
09cacce7
TL
28 var selModeField = Ext.create('Proxmox.form.KVComboBox', {
29 xtype: 'proxmoxKVComboBox',
a26c8bf7 30 comboItems: [
ad74d86c
DM
31 ['include', gettext('Include selected VMs')],
32 ['all', gettext('All')],
ab648869 33 ['exclude', gettext('Exclude selected VMs')],
a82afef0 34 ['pool', gettext('Pool based')]
ad74d86c
DM
35 ],
36 fieldLabel: gettext('Selection mode'),
37 name: 'selMode',
38 value: ''
39 });
40
ad74d86c
DM
41 var sm = Ext.create('Ext.selection.CheckboxModel', {
42 mode: 'SIMPLE',
43 listeners: {
44 selectionchange: function(model, selected) {
be8b69aa
TL
45 var sel = [];
46 Ext.Array.each(selected, function(record) {
47 sel.push(record.data.vmid);
48 });
49
50 // to avoid endless recursion suspend the vmidField change
51 // event temporary as it calls us again
52 vmidField.suspendEvent('change');
53 vmidField.setValue(sel);
54 vmidField.resumeEvent('change');
ad74d86c
DM
55 }
56 }
57 });
58
59 var storagesel = Ext.create('PVE.form.StorageSelector', {
60 fieldLabel: gettext('Storage'),
61 nodename: 'localhost',
62 storageContent: 'backup',
63 allowBlank: false,
64 name: 'storage'
65 });
66
67 var store = new Ext.data.Store({
68 model: 'PVEResources',
60e049c2
TM
69 sorters: {
70 property: 'vmid',
71 order: 'ASC'
ad74d86c
DM
72 }
73 });
74
75 var vmgrid = Ext.createWidget('grid', {
76 store: store,
77 border: true,
78 height: 300,
79 selModel: sm,
80 disabled: true,
81 columns: [
60e049c2 82 {
ad74d86c
DM
83 header: 'ID',
84 dataIndex: 'vmid',
85 width: 60
86 },
60e049c2 87 {
ad74d86c
DM
88 header: gettext('Node'),
89 dataIndex: 'node'
90 },
60e049c2 91 {
ad74d86c
DM
92 header: gettext('Status'),
93 dataIndex: 'uptime',
94 renderer: function(value) {
95 if (value) {
e7ade592 96 return Proxmox.Utils.runningText;
ad74d86c 97 } else {
e7ade592 98 return Proxmox.Utils.stoppedText;
ad74d86c
DM
99 }
100 }
101 },
60e049c2
TM
102 {
103 header: gettext('Name'),
ad74d86c 104 dataIndex: 'name',
60e049c2 105 flex: 1
ad74d86c 106 },
60e049c2
TM
107 {
108 header: gettext('Type'),
ad74d86c
DM
109 dataIndex: 'type'
110 }
111 ]
112 });
113
ab648869
TM
114 var selectPoolMembers = function(poolid) {
115 if (!poolid) {
116 return;
117 }
118 sm.deselectAll(true);
119 store.filter([
120 {
121 id: 'poolFilter',
122 property: 'pool',
123 value: poolid
124 }
125 ]);
126 sm.selectAll(true);
127 };
128
129 var selPool = Ext.create('PVE.form.PoolSelector', {
a82afef0 130 fieldLabel: gettext('Pool to backup'),
ab648869
TM
131 hidden: true,
132 allowBlank: true,
133 name: 'pool',
134 listeners: {
135 change: function( selpool, newValue, oldValue) {
136 selectPoolMembers(newValue);
137 }
138 }
139 });
140
ad74d86c
DM
141 var nodesel = Ext.create('PVE.form.NodeSelector', {
142 name: 'node',
143 fieldLabel: gettext('Node'),
144 allowBlank: true,
145 editable: true,
146 autoSelect: false,
147 emptyText: '-- ' + gettext('All') + ' --',
148 listeners: {
149 change: function(f, value) {
150 storagesel.setNodename(value || 'localhost');
151 var mode = selModeField.getValue();
152 store.clearFilter();
153 store.filterBy(function(rec) {
154 return (!value || rec.get('node') === value);
155 });
156 if (mode === 'all') {
157 sm.selectAll(true);
158 }
ab648869
TM
159
160 if (mode === 'pool') {
161 selectPoolMembers(selPool.value);
162 }
ad74d86c
DM
163 }
164 }
165 });
166
167 var column1 = [
168 nodesel,
169 storagesel,
170 {
171 xtype: 'pveDayOfWeekSelector',
172 name: 'dow',
173 fieldLabel: gettext('Day of week'),
174 multiSelect: true,
175 value: ['sat'],
176 allowBlank: false
177 },
178 {
179 xtype: 'timefield',
180 fieldLabel: gettext('Start Time'),
181 name: 'starttime',
182 format: 'H:i',
4ecd1d07 183 formatText: 'HH:MM',
ad74d86c
DM
184 value: '00:00',
185 allowBlank: false
186 },
ab648869
TM
187 selModeField,
188 selPool
ad74d86c
DM
189 ];
190
191 var column2 = [
192 {
193 xtype: 'textfield',
194 fieldLabel: gettext('Send email to'),
195 name: 'mailto'
196 },
197 {
198 xtype: 'pveEmailNotificationSelector',
199 fieldLabel: gettext('Email notification'),
200 name: 'mailnotification',
d5e771ce
EK
201 deleteEmpty: me.isCreate ? false : true,
202 value: me.isCreate ? 'always' : ''
ad74d86c
DM
203 },
204 {
205 xtype: 'pveCompressionSelector',
206 fieldLabel: gettext('Compression'),
207 name: 'compress',
d5e771ce 208 deleteEmpty: me.isCreate ? false : true,
1c783f74 209 value: 'zstd'
ad74d86c
DM
210 },
211 {
212 xtype: 'pveBackupModeSelector',
213 fieldLabel: gettext('Mode'),
214 value: 'snapshot',
215 name: 'mode'
216 },
cfdc7ada 217 {
896c0d50 218 xtype: 'proxmoxcheckbox',
cfdc7ada
EK
219 fieldLabel: gettext('Enable'),
220 name: 'enabled',
221 uncheckedValue: 0,
222 defaultValue: 1,
223 checked: true
224 },
ad74d86c
DM
225 vmidField
226 ];
227
ef4ef788 228 var ipanel = Ext.create('Proxmox.panel.InputPanel', {
0de33b54 229 onlineHelp: 'chapter_vzdump',
ad74d86c
DM
230 column1: column1,
231 column2: column2,
232 onGetValues: function(values) {
233 if (!values.node) {
d5e771ce 234 if (!me.isCreate) {
60e049c2 235 Proxmox.Utils.assemble_field_data(values, { 'delete': 'node' });
ad74d86c
DM
236 }
237 delete values.node;
238 }
239
240 var selMode = values.selMode;
241 delete values.selMode;
242
243 if (selMode === 'all') {
244 values.all = 1;
245 values.exclude = '';
246 delete values.vmid;
247 } else if (selMode === 'exclude') {
248 values.all = 1;
249 values.exclude = values.vmid;
250 delete values.vmid;
ab648869
TM
251 } else if (selMode === 'pool') {
252 delete values.vmid;
253 }
254
255 if (selMode !== 'pool') {
256 delete values.pool;
ad74d86c
DM
257 }
258 return values;
259 }
260 });
261
262 var update_vmid_selection = function(list, mode) {
ab648869 263 if (mode !== 'all' && mode !== 'pool') {
ad74d86c
DM
264 sm.deselectAll(true);
265 if (list) {
266 Ext.Array.each(list.split(','), function(vmid) {
267 var rec = store.findRecord('vmid', vmid);
268 if (rec) {
269 sm.select(rec, true);
270 }
271 });
272 }
273 }
ad74d86c
DM
274 };
275
276 vmidField.on('change', function(f, value) {
277 var mode = selModeField.getValue();
278 update_vmid_selection(value, mode);
279 });
280
281 selModeField.on('change', function(f, value, oldValue) {
ab648869
TM
282 if (oldValue === 'pool') {
283 store.removeFilter('poolFilter');
284 }
285
286 if (oldValue === 'all') {
287 sm.deselectAll(true);
288 vmidField.setValue('');
289 }
290
ad74d86c
DM
291 if (value === 'all') {
292 sm.selectAll(true);
293 vmgrid.setDisabled(true);
294 } else {
295 vmgrid.setDisabled(false);
296 }
ab648869
TM
297
298 if (value === 'pool') {
299 vmgrid.setDisabled(true);
ad74d86c 300 vmidField.setValue('');
ab648869
TM
301 selPool.setVisible(true);
302 selPool.allowBlank = false;
303 selectPoolMembers(selPool.value);
304
305 } else {
306 selPool.setVisible(false);
307 selPool.allowBlank = true;
ad74d86c
DM
308 }
309 var list = vmidField.getValue();
310 update_vmid_selection(list, value);
311 });
60e049c2 312
ad74d86c
DM
313 var reload = function() {
314 store.load({
315 params: { type: 'vm' },
316 callback: function() {
317 var node = nodesel.getValue();
318 store.clearFilter();
319 store.filterBy(function(rec) {
163bf378 320 return (!node || node.length === 0 || rec.get('node') === node);
ad74d86c
DM
321 });
322 var list = vmidField.getValue();
323 var mode = selModeField.getValue();
324 if (mode === 'all') {
325 sm.selectAll(true);
ab648869
TM
326 } else if (mode === 'pool'){
327 selectPoolMembers(selPool.value);
ad74d86c
DM
328 } else {
329 update_vmid_selection(list, mode);
330 }
331 }
332 });
333 };
334
335 Ext.applyIf(me, {
336 subject: gettext("Backup Job"),
337 url: url,
338 method: method,
339 items: [ ipanel, vmgrid ]
340 });
341
342 me.callParent();
343
d5e771ce 344 if (me.isCreate) {
ad74d86c
DM
345 selModeField.setValue('include');
346 } else {
347 me.load({
348 success: function(response, options) {
349 var data = response.result.data;
350
351 data.dow = data.dow.split(',');
352
353 if (data.all || data.exclude) {
354 if (data.exclude) {
355 data.vmid = data.exclude;
356 data.selMode = 'exclude';
357 } else {
358 data.vmid = '';
359 data.selMode = 'all';
360 }
ab648869
TM
361 } else if (data.pool) {
362 data.selMode = 'pool';
363 data.selPool = data.pool;
ad74d86c
DM
364 } else {
365 data.selMode = 'include';
366 }
367
368 me.setValues(data);
369 }
370 });
371 }
372
373 reload();
374 }
375});
376
377
01ad47af
AL
378Ext.define('PVE.dc.BackupDiskTree', {
379 extend: 'Ext.tree.Panel',
380 alias: 'widget.pveBackupDiskTree',
381
382 folderSort: true,
383 rootVisible: false,
384
385 store: {
386 sorters: 'id',
387 data: {},
388 },
389
390 tools: [
391 {
392 type: 'expand',
393 tooltip: gettext('Expand All'),
394 scope: this,
395 callback: function(panel) {
396 panel.expandAll();
397 },
398 },
399 {
400 type: 'collapse',
401 tooltip: gettext('Collapse All'),
402 scope: this,
403 callback: function(panel) {
404 panel.collapseAll();
405 }
406 },
407 ],
408
409 columns: [
410 {
411 xtype: 'treecolumn',
412 text: gettext('Guest Image'),
413 renderer: function(value, meta, record) {
414 if (record.data.type) {
415 // guest level
416 let ret = value;
417 if (record.data.name) {
418 ret += " (" + record.data.name + ")";
419 }
420 return ret;
421 } else {
422 // volume level
423 // extJS needs unique IDs but we only want to show the
424 // volumes key from "vmid:key"
425 return value.split(':')[1] + " - " + record.data.name;
426 }
427 },
428 dataIndex: 'id',
429 flex: 6,
430 },
431 {
432 text: gettext('Type'),
433 dataIndex: 'type',
434 flex: 1,
435 },
436 {
437 text: gettext('Backup Job'),
438 renderer: PVE.Utils.render_backup_status,
439 dataIndex: 'included',
440 flex: 3,
441 },
442 ],
443
444 reload: function() {
445 var me = this;
446 var sm = me.getSelectionModel();
447
448 Proxmox.Utils.API2Request({
449 url: "/cluster/backup/" + me.jobid + "/included_volumes",
450 waitMsgTarget: me,
451 method: 'GET',
452 failure: function(response, opts) {
453 Proxmox.Utils.setErrorMask(me, response.htmlStatus);
454 },
455 success: function(response, opts) {
456 sm.deselectAll();
457 me.setRootNode(response.result.data);
458 me.expandAll();
459 },
460 });
461 },
462
463 initComponent: function() {
464 var me = this;
465
466 if (!me.jobid) {
467 throw "no job id specified";
468 }
469
470 var sm = Ext.create('Ext.selection.TreeModel', {});
471
472 Ext.apply(me, {
473 selModel: sm,
474 fields: ['id', 'type',
475 {
476 type: 'string',
477 name: 'iconCls',
478 calculate: function(data) {
479 var txt = 'fa x-fa-tree fa-';
480 if (data.leaf && !data.type) {
481 return txt + 'hdd-o';
482 } else if (data.type === 'qemu') {
483 return txt + 'desktop';
484 } else if (data.type === 'lxc') {
485 return txt + 'cube';
486 } else {
487 return txt + 'question-circle';
488 }
489 }
490 }
491 ],
e4b95752
TL
492 header: {
493 items: [{
01ad47af 494 xtype: 'textfield',
e4b95752
TL
495 fieldLabel: gettext('Search'),
496 labelWidth: 50,
497 emptyText: 'Name, VMID, Type',
01ad47af 498 width: 200,
e4b95752 499 padding: '0 5 0 0',
01ad47af
AL
500 enableKeyEvents: true,
501 listeners: {
502 buffer: 500,
503 keyup: function(field) {
504 let searchValue = field.getValue();
505 searchValue = searchValue.toLowerCase();
506
507 me.store.clearFilter(true);
508 me.store.filterBy(function(record) {
509 let match = false;
510
511 let data = '';
512 if (record.data.depth == 0) {
513 return true;
514 } else if (record.data.depth == 1) {
515 data = record.data;
516 } else if (record.data.depth == 2) {
517 data = record.parentNode.data;
518 }
519
520 Ext.each(['name', 'id', 'type'], function(property) {
e4b95752 521 if (data[property] === null) {
01ad47af
AL
522 return;
523 }
524
525 let v = data[property].toString();
526 if (v !== undefined) {
527 v = v.toLowerCase();
528 if (v.includes(searchValue)) {
529 match = true;
530 return;
531 }
532 }
533 });
534 return match;
535 });
536 }
537 }
538 }
e4b95752 539 ]},
01ad47af
AL
540 });
541
542 me.callParent();
543
544 me.reload();
545 }
546});
547
548Ext.define('PVE.dc.BackupInfo', {
549 extend: 'Proxmox.panel.InputPanel',
550 alias: 'widget.pveBackupInfo',
551
552 padding: 10,
553
554 column1: [
555 {
556 name: 'node',
557 fieldLabel: gettext('Node'),
558 xtype: 'displayfield',
559 renderer: function (value) {
560 if (!value) {
561 return '-- ' + gettext('All') + ' --';
562 } else {
563 return value;
564 }
565 },
566 },
567 {
568 name: 'storage',
569 fieldLabel: gettext('Storage'),
570 xtype: 'displayfield',
571 },
572 {
573 name: 'dow',
574 fieldLabel: gettext('Day of week'),
575 xtype: 'displayfield',
576 renderer: PVE.Utils.render_backup_days_of_week,
577 },
578 {
579 name: 'starttime',
580 fieldLabel: gettext('Start Time'),
581 xtype: 'displayfield',
582 },
583 {
584 name: 'selMode',
585 fieldLabel: gettext('Selection mode'),
586 xtype: 'displayfield',
587 },
588 {
589 name: 'pool',
590 fieldLabel: gettext('Pool to backup'),
591 xtype: 'displayfield',
592 }
593 ],
594 column2: [
595 {
596 name: 'mailto',
597 fieldLabel: gettext('Send email to'),
598 xtype: 'displayfield',
599 },
600 {
601 name: 'mailnotification',
602 fieldLabel: gettext('Email notification'),
603 xtype: 'displayfield',
604 renderer: function (value) {
605 let msg;
606 switch (value) {
607 case 'always':
608 msg = gettext('Always');
609 break;
610 case 'failure':
611 msg = gettext('On failure only');
612 break;
613 }
614 return msg;
615 },
616 },
617 {
618 name: 'compress',
619 fieldLabel: gettext('Compression'),
620 xtype: 'displayfield',
621 },
622 {
623 name: 'mode',
624 fieldLabel: gettext('Mode'),
625 xtype: 'displayfield',
626 renderer: function (value) {
627 let msg;
628 switch (value) {
629 case 'snapshot':
630 msg = gettext('Snapshot');
631 break;
632 case 'suspend':
633 msg = gettext('Suspend');
634 break;
635 case 'stop':
636 msg = gettext('Stop');
637 break;
638 }
639 return msg;
640 },
641 },
642 {
643 name: 'enabled',
644 fieldLabel: gettext('Enabled'),
645 xtype: 'displayfield',
646 renderer: function (value) {
647 if (PVE.Parser.parseBoolean(value.toString())) {
648 return gettext('Yes');
649 } else {
650 return gettext('No');
651 }
652 },
653 },
654 ],
655
656 setValues: function(values) {
657 var me = this;
658
659 Ext.iterate(values, function(fieldId, val) {
660 let field = me.query('[isFormField][name=' + fieldId + ']')[0];
661 if (field) {
662 field.setValue(val);
663 }
664 });
665
666 // selection Mode depends on the presence/absence of several keys
667 let selModeField = me.query('[isFormField][name=selMode]')[0];
668 let selMode = 'none';
669 if (values.vmid) {
670 selMode = gettext('Include selected VMs');
671 }
672 if (values.all) {
673 selMode = gettext('All');
674 }
675 if (values.exclude) {
676 selMode = gettext('Exclude selected VMs');
677 }
678 if (values.pool) {
679 selMode = gettext('Pool based');
680 }
681 selModeField.setValue(selMode);
682
683 if (!values.pool) {
684 let poolField = me.query('[isFormField][name=pool]')[0];
685 poolField.setVisible(0);
686 }
687 },
688
689 initComponent: function() {
690 var me = this;
691
692 if (!me.record) {
693 throw "no data provided";
694 }
695 me.callParent();
696
697 me.setValues(me.record);
698 }
699});
700
7d2fac4a
AL
701
702Ext.define('PVE.dc.BackedGuests', {
703 extend: 'Ext.grid.GridPanel',
704 alias: 'widget.pveBackedGuests',
705
706 textfilter: '',
707
708 columns: [
709 {
710 header: gettext('Type'),
711 dataIndex: "type",
712 renderer: PVE.Utils.render_resource_type,
713 flex: 1,
714 sortable: true,
715 },
716 {
717 header: gettext('VMID'),
718 dataIndex: 'vmid',
719 flex: 1,
720 sortable: true,
721 },
722 {
723 header: gettext('Name'),
724 dataIndex: 'name',
725 flex: 2,
726 sortable: true,
727 },
728 ],
729
730 initComponent: function() {
731 let me = this;
732
733 me.store.clearFilter(true);
734
735 Ext.apply(me, {
736 stateful: true,
737 stateId: 'grid-dc-backed-guests',
738 tbar: [
739 '->',
740 gettext('Search') + ':', ' ',
741 {
742 xtype: 'textfield',
743 width: 200,
744 enableKeyEvents: true,
745 listeners: {
746 buffer: 500,
747 keyup: function(field) {
748 let searchValue = field.getValue();
749 searchValue = searchValue.toLowerCase();
750
751 me.store.clearFilter(true);
752 me.store.filterBy(function(record) {
753 let match = false;
754
755 Ext.each(['name', 'vmid', 'type'], function(property) {
756 if (record.data[property] == null) {
757 return;
758 }
759
760 let v = record.data[property].toString();
761 if (v !== undefined) {
762 v = v.toLowerCase();
763 if (v.includes(searchValue)) {
764 match = true;
765 return;
766 }
767 }
768 });
769 return match;
770 });
771 }
772 }
773 }
774 ],
775 viewConfig: {
776 stripeRows: true,
777 trackOver: false,
778 },
779 });
780 me.callParent();
781 },
782});
783
ad74d86c
DM
784Ext.define('PVE.dc.BackupView', {
785 extend: 'Ext.grid.GridPanel',
786
787 alias: ['widget.pveDcBackupView'],
788
ba93a9c6
DC
789 onlineHelp: 'chapter_vzdump',
790
ad74d86c 791 allText: '-- ' + gettext('All') + ' --',
ad74d86c
DM
792
793 initComponent : function() {
794 var me = this;
795
796 var store = new Ext.data.Store({
797 model: 'pve-cluster-backup',
798 proxy: {
56a353b9 799 type: 'proxmox',
ad74d86c
DM
800 url: "/api2/json/cluster/backup"
801 }
802 });
803
7d2fac4a
AL
804 var not_backed_store = new Ext.data.Store({
805 sorters: 'vmid',
806 proxy:{
807 type: 'proxmox',
808 url: 'api2/json/cluster/backupinfo/not_backed_up',
809 },
810 });
811
ad74d86c
DM
812 var reload = function() {
813 store.load();
7d2fac4a
AL
814 not_backed_store.load({
815 callback: function(records, operation, success) {
816 if (records.length) {
817 not_backed_warning.setVisible(true);
818 not_backed_btn.setVisible(true);
819 } else {
820 not_backed_warning.setVisible(false);
821 not_backed_btn.setVisible(false);
822 }
823 },
824 });
ad74d86c
DM
825 };
826
827 var sm = Ext.create('Ext.selection.RowModel', {});
828
829 var run_editor = function() {
830 var rec = sm.getSelection()[0];
831 if (!rec) {
832 return;
833 }
834
43b2494b
SR
835 var win = Ext.create('PVE.dc.BackupEdit', {
836 jobid: rec.data.id
837 });
838 win.on('destroy', reload);
839 win.show();
ad74d86c
DM
840 };
841
01ad47af
AL
842 var run_detail = function() {
843 let record = sm.getSelection()[0]
844 if (!record) {
845 return;
846 }
847 var me = this;
848 var infoview = Ext.create('PVE.dc.BackupInfo', {
849 flex: 0,
850 layout: 'fit',
851 record: record.data,
852 });
853 var disktree = Ext.create('PVE.dc.BackupDiskTree', {
854 title: gettext('Included disks'),
855 flex: 1,
856 jobid: record.data.id,
857 });
858
859 var win = Ext.create('Ext.window.Window', {
860 modal: true,
861 width: 600,
862 height: 500,
863 stateful: true,
864 stateId: 'backup-detail-view',
865 resizable: true,
866 layout: 'fit',
867 title: gettext('Backup Details'),
868
869 items:[{
870 xtype: 'panel',
871 region: 'center',
872 layout: {
873 type: 'vbox',
874 align: 'stretch'
875 },
876 items: [infoview, disktree],
877 }]
878 }).show();
879 };
880
389d3cf1
SR
881 var run_backup_now = function(job) {
882 job = Ext.clone(job);
883
a0fecb88 884 let jobNode = job.node;
389d3cf1
SR
885 // Remove properties related to scheduling
886 delete job.enabled;
887 delete job.starttime;
888 delete job.dow;
889 delete job.id;
890 delete job.node;
891 job.all = job.all === true ? 1 : 0;
892
a0fecb88
TL
893 let allNodes = PVE.data.ResourceStore.getNodes();
894 let nodes = allNodes.filter(node => node.status === 'online').map(node => node.node);
895 let errors = [];
896
897 if (jobNode !== undefined) {
898 if (!nodes.includes(jobNode)) {
899 Ext.Msg.alert('Error', "Node '"+ jobNode +"' from backup job isn't online!");
900 return;
901 }
902 nodes = [ jobNode ];
903 } else {
904 let unkownNodes = allNodes.filter(node => node.status !== 'online');
905 if (unkownNodes.length > 0)
906 errors.push(unkownNodes.map(node => node.node + ": " + gettext("Node is offline")));
907 }
908 let jobTotalCount = nodes.length, jobsStarted = 0;
389d3cf1
SR
909
910 Ext.Msg.show({
911 title: gettext('Please wait...'),
912 closable: false,
a0fecb88
TL
913 progress: true,
914 progressText: '0/' + jobTotalCount,
389d3cf1 915 });
389d3cf1 916
a0fecb88
TL
917 let postRequest = function () {
918 jobsStarted++;
919 Ext.Msg.updateProgress(jobsStarted / jobTotalCount, jobsStarted + '/' + jobTotalCount);
389d3cf1 920
a0fecb88 921 if (jobsStarted == jobTotalCount) {
389d3cf1 922 Ext.Msg.hide();
a0fecb88
TL
923 if (errors.length > 0) {
924 Ext.Msg.alert('Error', 'Some errors have been encountered:<br />' + errors.join('<br />'));
389d3cf1
SR
925 }
926 }
a0fecb88
TL
927 };
928
929 nodes.forEach(node => Proxmox.Utils.API2Request({
930 url: '/nodes/' + node + '/vzdump',
931 method: 'POST',
932 params: job,
933 failure: function (response, opts) {
934 errors.push(node + ': ' + response.htmlStatus);
935 postRequest();
936 },
937 success: postRequest
938 }));
939 };
389d3cf1 940
7d2fac4a
AL
941 var run_show_not_backed = function() {
942 var me = this;
943 var backedinfo = Ext.create('PVE.dc.BackedGuests', {
944 flex: 1,
945 layout: 'fit',
946 store: not_backed_store,
947 });
948
949 var win = Ext.create('Ext.window.Window', {
950 modal: true,
951 width: 600,
952 height: 500,
953 resizable: true,
954 layout: 'fit',
955 title: gettext('Guests without backup job'),
956
957 items:[{
958 xtype: 'panel',
959 region: 'center',
960 layout: {
961 type: 'vbox',
962 align: 'stretch'
963 },
964 items: [backedinfo],
965 }]
966 }).show();
967 };
968
5720fafa 969 var edit_btn = new Proxmox.button.Button({
ad74d86c
DM
970 text: gettext('Edit'),
971 disabled: true,
972 selModel: sm,
973 handler: run_editor
974 });
975
389d3cf1
SR
976 var run_btn = new Proxmox.button.Button({
977 text: gettext('Run now'),
978 disabled: true,
979 selModel: sm,
980 handler: function() {
981 var rec = sm.getSelection()[0];
982 if (!rec) {
983 return;
984 }
985
986 Ext.Msg.show({
987 title: gettext('Confirm'),
988 icon: Ext.Msg.QUESTION,
989 msg: gettext('Start the selected backup job now?'),
990 buttons: Ext.Msg.YESNO,
991 callback: function(btn) {
992 if (btn !== 'yes') {
993 return;
994 }
995 run_backup_now(rec.data);
996 }
997 });
998 }
999 });
1000
3b1ca3ff 1001 var remove_btn = Ext.create('Proxmox.button.StdRemoveButton', {
ad74d86c 1002 selModel: sm,
3b1ca3ff
DC
1003 baseurl: '/cluster/backup',
1004 callback: function() {
1005 reload();
ad74d86c
DM
1006 }
1007 });
1008
01ad47af
AL
1009 var detail_btn = new Proxmox.button.Button({
1010 text: gettext('Detail'),
1011 disabled: true,
1012 tooltip: gettext('Show job details and which guests and volumes are affected by the backup job'),
1013 selModel: sm,
1014 handler: run_detail,
1015 });
1016
7d2fac4a
AL
1017 var not_backed_warning = Ext.create('Ext.toolbar.TextItem', {
1018 html: '<i class="fa fa-fw fa-exclamation-circle"></i>' + gettext('Some guests are not covered by any backup job.'),
1019 hidden: true,
1020 });
1021
1022 var not_backed_btn = new Proxmox.button.Button({
1023 text: gettext('Show'),
1024 hidden: true,
1025 handler: run_show_not_backed,
1026 });
1027
e7ade592 1028 Proxmox.Utils.monStoreErrors(me, store);
ad74d86c
DM
1029
1030 Ext.apply(me, {
1031 store: store,
1032 selModel: sm,
c4bb9405
DC
1033 stateful: true,
1034 stateId: 'grid-dc-backup',
ad74d86c
DM
1035 viewConfig: {
1036 trackOver: false
1037 },
1038 tbar: [
1039 {
1040 text: gettext('Add'),
1041 handler: function() {
1042 var win = Ext.create('PVE.dc.BackupEdit',{});
1043 win.on('destroy', reload);
1044 win.show();
1045 }
1046 },
7a04ce23 1047 '-',
ad74d86c 1048 remove_btn,
389d3cf1 1049 edit_btn,
01ad47af
AL
1050 detail_btn,
1051 '-',
1052 run_btn,
7d2fac4a
AL
1053 '->',
1054 not_backed_warning,
1055 not_backed_btn,
60e049c2 1056 ],
ad74d86c 1057 columns: [
cfdc7ada
EK
1058 {
1059 header: gettext('Enabled'),
ae9e2161 1060 width: 80,
cfdc7ada 1061 dataIndex: 'enabled',
370ed4c8 1062 xtype: 'checkcolumn',
cfdc7ada 1063 sortable: true,
370ed4c8
DC
1064 disabled: true,
1065 disabledCls: 'x-item-enabled',
22f2f9d6 1066 stopSelection: false
cfdc7ada 1067 },
ad74d86c
DM
1068 {
1069 header: gettext('Node'),
1070 width: 100,
1071 sortable: true,
1072 dataIndex: 'node',
1073 renderer: function(value) {
1074 if (value) {
1075 return value;
1076 }
1077 return me.allText;
1078 }
1079 },
1080 {
1081 header: gettext('Day of week'),
1082 width: 200,
1083 sortable: false,
2bdf9dd3 1084 dataIndex: 'dow',
7f08d0d1 1085 renderer: PVE.Utils.render_backup_days_of_week
ad74d86c
DM
1086 },
1087 {
1088 header: gettext('Start Time'),
1089 width: 60,
1090 sortable: true,
1091 dataIndex: 'starttime'
1092 },
1093 {
1094 header: gettext('Storage'),
1095 width: 100,
1096 sortable: true,
1097 dataIndex: 'storage'
1098 },
1099 {
1100 header: gettext('Selection'),
1101 flex: 1,
1102 sortable: false,
1103 dataIndex: 'vmid',
7f08d0d1 1104 renderer: PVE.Utils.render_backup_selection
ad74d86c
DM
1105 }
1106 ],
1107 listeners: {
c0b3df6e 1108 activate: reload,
ad74d86c
DM
1109 itemdblclick: run_editor
1110 }
1111 });
60e049c2 1112
ad74d86c
DM
1113 me.callParent();
1114 }
1115}, function() {
1116
1117 Ext.define('pve-cluster-backup', {
1118 extend: 'Ext.data.Model',
60e049c2 1119 fields: [
ad74d86c
DM
1120 'id', 'starttime', 'dow',
1121 'storage', 'node', 'vmid', 'exclude',
389d3cf1 1122 'mailto', 'pool', 'compress', 'mode',
cfdc7ada 1123 { name: 'enabled', type: 'boolean' },
389d3cf1 1124 { name: 'all', type: 'boolean' }
ad74d86c
DM
1125 ]
1126 });
cfdc7ada 1127});