]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/dc/Backup.js
fix #2609 gui: backup: add window for not backed guests
[pve-manager.git] / www / manager6 / dc / Backup.js
1 Ext.define('PVE.dc.BackupEdit', {
2 extend: 'Proxmox.window.Edit',
3 alias: ['widget.pveDcBackupEdit'],
4
5 defaultFocus: undefined,
6
7 initComponent : function() {
8 var me = this;
9
10 me.isCreate = !me.jobid;
11
12 var url;
13 var method;
14
15 if (me.isCreate) {
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
27 // 'value' can be assigned a string or an array
28 var selModeField = Ext.create('Proxmox.form.KVComboBox', {
29 xtype: 'proxmoxKVComboBox',
30 comboItems: [
31 ['include', gettext('Include selected VMs')],
32 ['all', gettext('All')],
33 ['exclude', gettext('Exclude selected VMs')],
34 ['pool', gettext('Pool based')]
35 ],
36 fieldLabel: gettext('Selection mode'),
37 name: 'selMode',
38 value: ''
39 });
40
41 var sm = Ext.create('Ext.selection.CheckboxModel', {
42 mode: 'SIMPLE',
43 listeners: {
44 selectionchange: function(model, selected) {
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');
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',
69 sorters: {
70 property: 'vmid',
71 order: 'ASC'
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: [
82 {
83 header: 'ID',
84 dataIndex: 'vmid',
85 width: 60
86 },
87 {
88 header: gettext('Node'),
89 dataIndex: 'node'
90 },
91 {
92 header: gettext('Status'),
93 dataIndex: 'uptime',
94 renderer: function(value) {
95 if (value) {
96 return Proxmox.Utils.runningText;
97 } else {
98 return Proxmox.Utils.stoppedText;
99 }
100 }
101 },
102 {
103 header: gettext('Name'),
104 dataIndex: 'name',
105 flex: 1
106 },
107 {
108 header: gettext('Type'),
109 dataIndex: 'type'
110 }
111 ]
112 });
113
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', {
130 fieldLabel: gettext('Pool to backup'),
131 hidden: true,
132 allowBlank: true,
133 name: 'pool',
134 listeners: {
135 change: function( selpool, newValue, oldValue) {
136 selectPoolMembers(newValue);
137 }
138 }
139 });
140
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 }
159
160 if (mode === 'pool') {
161 selectPoolMembers(selPool.value);
162 }
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',
183 formatText: 'HH:MM',
184 value: '00:00',
185 allowBlank: false
186 },
187 selModeField,
188 selPool
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',
201 deleteEmpty: me.isCreate ? false : true,
202 value: me.isCreate ? 'always' : ''
203 },
204 {
205 xtype: 'pveCompressionSelector',
206 fieldLabel: gettext('Compression'),
207 name: 'compress',
208 deleteEmpty: me.isCreate ? false : true,
209 value: 'zstd'
210 },
211 {
212 xtype: 'pveBackupModeSelector',
213 fieldLabel: gettext('Mode'),
214 value: 'snapshot',
215 name: 'mode'
216 },
217 {
218 xtype: 'proxmoxcheckbox',
219 fieldLabel: gettext('Enable'),
220 name: 'enabled',
221 uncheckedValue: 0,
222 defaultValue: 1,
223 checked: true
224 },
225 vmidField
226 ];
227
228 var ipanel = Ext.create('Proxmox.panel.InputPanel', {
229 onlineHelp: 'chapter_vzdump',
230 column1: column1,
231 column2: column2,
232 onGetValues: function(values) {
233 if (!values.node) {
234 if (!me.isCreate) {
235 Proxmox.Utils.assemble_field_data(values, { 'delete': 'node' });
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;
251 } else if (selMode === 'pool') {
252 delete values.vmid;
253 }
254
255 if (selMode !== 'pool') {
256 delete values.pool;
257 }
258 return values;
259 }
260 });
261
262 var update_vmid_selection = function(list, mode) {
263 if (mode !== 'all' && mode !== 'pool') {
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 }
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) {
282 if (oldValue === 'pool') {
283 store.removeFilter('poolFilter');
284 }
285
286 if (oldValue === 'all') {
287 sm.deselectAll(true);
288 vmidField.setValue('');
289 }
290
291 if (value === 'all') {
292 sm.selectAll(true);
293 vmgrid.setDisabled(true);
294 } else {
295 vmgrid.setDisabled(false);
296 }
297
298 if (value === 'pool') {
299 vmgrid.setDisabled(true);
300 vmidField.setValue('');
301 selPool.setVisible(true);
302 selPool.allowBlank = false;
303 selectPoolMembers(selPool.value);
304
305 } else {
306 selPool.setVisible(false);
307 selPool.allowBlank = true;
308 }
309 var list = vmidField.getValue();
310 update_vmid_selection(list, value);
311 });
312
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) {
320 return (!node || node.length === 0 || rec.get('node') === node);
321 });
322 var list = vmidField.getValue();
323 var mode = selModeField.getValue();
324 if (mode === 'all') {
325 sm.selectAll(true);
326 } else if (mode === 'pool'){
327 selectPoolMembers(selPool.value);
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
344 if (me.isCreate) {
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 }
361 } else if (data.pool) {
362 data.selMode = 'pool';
363 data.selPool = data.pool;
364 } else {
365 data.selMode = 'include';
366 }
367
368 me.setValues(data);
369 }
370 });
371 }
372
373 reload();
374 }
375 });
376
377
378 Ext.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 ],
492 tbar: [
493 '->',
494 gettext('Search') + ':', ' ',
495 {
496 xtype: 'textfield',
497 width: 200,
498 enableKeyEvents: true,
499 listeners: {
500 buffer: 500,
501 keyup: function(field) {
502 let searchValue = field.getValue();
503 searchValue = searchValue.toLowerCase();
504
505 me.store.clearFilter(true);
506 me.store.filterBy(function(record) {
507 let match = false;
508
509 let data = '';
510 if (record.data.depth == 0) {
511 return true;
512 } else if (record.data.depth == 1) {
513 data = record.data;
514 } else if (record.data.depth == 2) {
515 data = record.parentNode.data;
516 }
517
518 Ext.each(['name', 'id', 'type'], function(property) {
519 if (data[property] == null) {
520 return;
521 }
522
523 let v = data[property].toString();
524 if (v !== undefined) {
525 v = v.toLowerCase();
526 if (v.includes(searchValue)) {
527 match = true;
528 return;
529 }
530 }
531 });
532 return match;
533 });
534 }
535 }
536 }
537 ],
538 });
539
540 me.callParent();
541
542 me.reload();
543 }
544 });
545
546 Ext.define('PVE.dc.BackupInfo', {
547 extend: 'Proxmox.panel.InputPanel',
548 alias: 'widget.pveBackupInfo',
549
550 padding: 10,
551
552 column1: [
553 {
554 name: 'node',
555 fieldLabel: gettext('Node'),
556 xtype: 'displayfield',
557 renderer: function (value) {
558 if (!value) {
559 return '-- ' + gettext('All') + ' --';
560 } else {
561 return value;
562 }
563 },
564 },
565 {
566 name: 'storage',
567 fieldLabel: gettext('Storage'),
568 xtype: 'displayfield',
569 },
570 {
571 name: 'dow',
572 fieldLabel: gettext('Day of week'),
573 xtype: 'displayfield',
574 renderer: PVE.Utils.render_backup_days_of_week,
575 },
576 {
577 name: 'starttime',
578 fieldLabel: gettext('Start Time'),
579 xtype: 'displayfield',
580 },
581 {
582 name: 'selMode',
583 fieldLabel: gettext('Selection mode'),
584 xtype: 'displayfield',
585 },
586 {
587 name: 'pool',
588 fieldLabel: gettext('Pool to backup'),
589 xtype: 'displayfield',
590 }
591 ],
592 column2: [
593 {
594 name: 'mailto',
595 fieldLabel: gettext('Send email to'),
596 xtype: 'displayfield',
597 },
598 {
599 name: 'mailnotification',
600 fieldLabel: gettext('Email notification'),
601 xtype: 'displayfield',
602 renderer: function (value) {
603 let msg;
604 switch (value) {
605 case 'always':
606 msg = gettext('Always');
607 break;
608 case 'failure':
609 msg = gettext('On failure only');
610 break;
611 }
612 return msg;
613 },
614 },
615 {
616 name: 'compress',
617 fieldLabel: gettext('Compression'),
618 xtype: 'displayfield',
619 },
620 {
621 name: 'mode',
622 fieldLabel: gettext('Mode'),
623 xtype: 'displayfield',
624 renderer: function (value) {
625 let msg;
626 switch (value) {
627 case 'snapshot':
628 msg = gettext('Snapshot');
629 break;
630 case 'suspend':
631 msg = gettext('Suspend');
632 break;
633 case 'stop':
634 msg = gettext('Stop');
635 break;
636 }
637 return msg;
638 },
639 },
640 {
641 name: 'enabled',
642 fieldLabel: gettext('Enabled'),
643 xtype: 'displayfield',
644 renderer: function (value) {
645 if (PVE.Parser.parseBoolean(value.toString())) {
646 return gettext('Yes');
647 } else {
648 return gettext('No');
649 }
650 },
651 },
652 ],
653
654 setValues: function(values) {
655 var me = this;
656
657 Ext.iterate(values, function(fieldId, val) {
658 let field = me.query('[isFormField][name=' + fieldId + ']')[0];
659 if (field) {
660 field.setValue(val);
661 }
662 });
663
664 // selection Mode depends on the presence/absence of several keys
665 let selModeField = me.query('[isFormField][name=selMode]')[0];
666 let selMode = 'none';
667 if (values.vmid) {
668 selMode = gettext('Include selected VMs');
669 }
670 if (values.all) {
671 selMode = gettext('All');
672 }
673 if (values.exclude) {
674 selMode = gettext('Exclude selected VMs');
675 }
676 if (values.pool) {
677 selMode = gettext('Pool based');
678 }
679 selModeField.setValue(selMode);
680
681 if (!values.pool) {
682 let poolField = me.query('[isFormField][name=pool]')[0];
683 poolField.setVisible(0);
684 }
685 },
686
687 initComponent: function() {
688 var me = this;
689
690 if (!me.record) {
691 throw "no data provided";
692 }
693 me.callParent();
694
695 me.setValues(me.record);
696 }
697 });
698
699
700 Ext.define('PVE.dc.BackedGuests', {
701 extend: 'Ext.grid.GridPanel',
702 alias: 'widget.pveBackedGuests',
703
704 textfilter: '',
705
706 columns: [
707 {
708 header: gettext('Type'),
709 dataIndex: "type",
710 renderer: PVE.Utils.render_resource_type,
711 flex: 1,
712 sortable: true,
713 },
714 {
715 header: gettext('VMID'),
716 dataIndex: 'vmid',
717 flex: 1,
718 sortable: true,
719 },
720 {
721 header: gettext('Name'),
722 dataIndex: 'name',
723 flex: 2,
724 sortable: true,
725 },
726 ],
727
728 initComponent: function() {
729 let me = this;
730
731 me.store.clearFilter(true);
732
733 Ext.apply(me, {
734 stateful: true,
735 stateId: 'grid-dc-backed-guests',
736 tbar: [
737 '->',
738 gettext('Search') + ':', ' ',
739 {
740 xtype: 'textfield',
741 width: 200,
742 enableKeyEvents: true,
743 listeners: {
744 buffer: 500,
745 keyup: function(field) {
746 let searchValue = field.getValue();
747 searchValue = searchValue.toLowerCase();
748
749 me.store.clearFilter(true);
750 me.store.filterBy(function(record) {
751 let match = false;
752
753 Ext.each(['name', 'vmid', 'type'], function(property) {
754 if (record.data[property] == null) {
755 return;
756 }
757
758 let v = record.data[property].toString();
759 if (v !== undefined) {
760 v = v.toLowerCase();
761 if (v.includes(searchValue)) {
762 match = true;
763 return;
764 }
765 }
766 });
767 return match;
768 });
769 }
770 }
771 }
772 ],
773 viewConfig: {
774 stripeRows: true,
775 trackOver: false,
776 },
777 });
778 me.callParent();
779 },
780 });
781
782 Ext.define('PVE.dc.BackupView', {
783 extend: 'Ext.grid.GridPanel',
784
785 alias: ['widget.pveDcBackupView'],
786
787 onlineHelp: 'chapter_vzdump',
788
789 allText: '-- ' + gettext('All') + ' --',
790
791 initComponent : function() {
792 var me = this;
793
794 var store = new Ext.data.Store({
795 model: 'pve-cluster-backup',
796 proxy: {
797 type: 'proxmox',
798 url: "/api2/json/cluster/backup"
799 }
800 });
801
802 var not_backed_store = new Ext.data.Store({
803 sorters: 'vmid',
804 proxy:{
805 type: 'proxmox',
806 url: 'api2/json/cluster/backupinfo/not_backed_up',
807 },
808 });
809
810 var reload = function() {
811 store.load();
812 not_backed_store.load({
813 callback: function(records, operation, success) {
814 if (records.length) {
815 not_backed_warning.setVisible(true);
816 not_backed_btn.setVisible(true);
817 } else {
818 not_backed_warning.setVisible(false);
819 not_backed_btn.setVisible(false);
820 }
821 },
822 });
823 };
824
825 var sm = Ext.create('Ext.selection.RowModel', {});
826
827 var run_editor = function() {
828 var rec = sm.getSelection()[0];
829 if (!rec) {
830 return;
831 }
832
833 var win = Ext.create('PVE.dc.BackupEdit', {
834 jobid: rec.data.id
835 });
836 win.on('destroy', reload);
837 win.show();
838 };
839
840 var run_detail = function() {
841 let record = sm.getSelection()[0]
842 if (!record) {
843 return;
844 }
845 var me = this;
846 var infoview = Ext.create('PVE.dc.BackupInfo', {
847 flex: 0,
848 layout: 'fit',
849 record: record.data,
850 });
851 var disktree = Ext.create('PVE.dc.BackupDiskTree', {
852 title: gettext('Included disks'),
853 flex: 1,
854 jobid: record.data.id,
855 });
856
857 var win = Ext.create('Ext.window.Window', {
858 modal: true,
859 width: 600,
860 height: 500,
861 stateful: true,
862 stateId: 'backup-detail-view',
863 resizable: true,
864 layout: 'fit',
865 title: gettext('Backup Details'),
866
867 items:[{
868 xtype: 'panel',
869 region: 'center',
870 layout: {
871 type: 'vbox',
872 align: 'stretch'
873 },
874 items: [infoview, disktree],
875 }]
876 }).show();
877 };
878
879 var run_backup_now = function(job) {
880 job = Ext.clone(job);
881
882 let jobNode = job.node;
883 // Remove properties related to scheduling
884 delete job.enabled;
885 delete job.starttime;
886 delete job.dow;
887 delete job.id;
888 delete job.node;
889 job.all = job.all === true ? 1 : 0;
890
891 let allNodes = PVE.data.ResourceStore.getNodes();
892 let nodes = allNodes.filter(node => node.status === 'online').map(node => node.node);
893 let errors = [];
894
895 if (jobNode !== undefined) {
896 if (!nodes.includes(jobNode)) {
897 Ext.Msg.alert('Error', "Node '"+ jobNode +"' from backup job isn't online!");
898 return;
899 }
900 nodes = [ jobNode ];
901 } else {
902 let unkownNodes = allNodes.filter(node => node.status !== 'online');
903 if (unkownNodes.length > 0)
904 errors.push(unkownNodes.map(node => node.node + ": " + gettext("Node is offline")));
905 }
906 let jobTotalCount = nodes.length, jobsStarted = 0;
907
908 Ext.Msg.show({
909 title: gettext('Please wait...'),
910 closable: false,
911 progress: true,
912 progressText: '0/' + jobTotalCount,
913 });
914
915 let postRequest = function () {
916 jobsStarted++;
917 Ext.Msg.updateProgress(jobsStarted / jobTotalCount, jobsStarted + '/' + jobTotalCount);
918
919 if (jobsStarted == jobTotalCount) {
920 Ext.Msg.hide();
921 if (errors.length > 0) {
922 Ext.Msg.alert('Error', 'Some errors have been encountered:<br />' + errors.join('<br />'));
923 }
924 }
925 };
926
927 nodes.forEach(node => Proxmox.Utils.API2Request({
928 url: '/nodes/' + node + '/vzdump',
929 method: 'POST',
930 params: job,
931 failure: function (response, opts) {
932 errors.push(node + ': ' + response.htmlStatus);
933 postRequest();
934 },
935 success: postRequest
936 }));
937 };
938
939 var run_show_not_backed = function() {
940 var me = this;
941 var backedinfo = Ext.create('PVE.dc.BackedGuests', {
942 flex: 1,
943 layout: 'fit',
944 store: not_backed_store,
945 });
946
947 var win = Ext.create('Ext.window.Window', {
948 modal: true,
949 width: 600,
950 height: 500,
951 resizable: true,
952 layout: 'fit',
953 title: gettext('Guests without backup job'),
954
955 items:[{
956 xtype: 'panel',
957 region: 'center',
958 layout: {
959 type: 'vbox',
960 align: 'stretch'
961 },
962 items: [backedinfo],
963 }]
964 }).show();
965 };
966
967 var edit_btn = new Proxmox.button.Button({
968 text: gettext('Edit'),
969 disabled: true,
970 selModel: sm,
971 handler: run_editor
972 });
973
974 var run_btn = new Proxmox.button.Button({
975 text: gettext('Run now'),
976 disabled: true,
977 selModel: sm,
978 handler: function() {
979 var rec = sm.getSelection()[0];
980 if (!rec) {
981 return;
982 }
983
984 Ext.Msg.show({
985 title: gettext('Confirm'),
986 icon: Ext.Msg.QUESTION,
987 msg: gettext('Start the selected backup job now?'),
988 buttons: Ext.Msg.YESNO,
989 callback: function(btn) {
990 if (btn !== 'yes') {
991 return;
992 }
993 run_backup_now(rec.data);
994 }
995 });
996 }
997 });
998
999 var remove_btn = Ext.create('Proxmox.button.StdRemoveButton', {
1000 selModel: sm,
1001 baseurl: '/cluster/backup',
1002 callback: function() {
1003 reload();
1004 }
1005 });
1006
1007 var detail_btn = new Proxmox.button.Button({
1008 text: gettext('Detail'),
1009 disabled: true,
1010 tooltip: gettext('Show job details and which guests and volumes are affected by the backup job'),
1011 selModel: sm,
1012 handler: run_detail,
1013 });
1014
1015 var not_backed_warning = Ext.create('Ext.toolbar.TextItem', {
1016 html: '<i class="fa fa-fw fa-exclamation-circle"></i>' + gettext('Some guests are not covered by any backup job.'),
1017 hidden: true,
1018 });
1019
1020 var not_backed_btn = new Proxmox.button.Button({
1021 text: gettext('Show'),
1022 hidden: true,
1023 handler: run_show_not_backed,
1024 });
1025
1026 Proxmox.Utils.monStoreErrors(me, store);
1027
1028 Ext.apply(me, {
1029 store: store,
1030 selModel: sm,
1031 stateful: true,
1032 stateId: 'grid-dc-backup',
1033 viewConfig: {
1034 trackOver: false
1035 },
1036 tbar: [
1037 {
1038 text: gettext('Add'),
1039 handler: function() {
1040 var win = Ext.create('PVE.dc.BackupEdit',{});
1041 win.on('destroy', reload);
1042 win.show();
1043 }
1044 },
1045 '-',
1046 remove_btn,
1047 edit_btn,
1048 detail_btn,
1049 '-',
1050 run_btn,
1051 '->',
1052 not_backed_warning,
1053 not_backed_btn,
1054 ],
1055 columns: [
1056 {
1057 header: gettext('Enabled'),
1058 width: 80,
1059 dataIndex: 'enabled',
1060 xtype: 'checkcolumn',
1061 sortable: true,
1062 disabled: true,
1063 disabledCls: 'x-item-enabled',
1064 stopSelection: false
1065 },
1066 {
1067 header: gettext('Node'),
1068 width: 100,
1069 sortable: true,
1070 dataIndex: 'node',
1071 renderer: function(value) {
1072 if (value) {
1073 return value;
1074 }
1075 return me.allText;
1076 }
1077 },
1078 {
1079 header: gettext('Day of week'),
1080 width: 200,
1081 sortable: false,
1082 dataIndex: 'dow',
1083 renderer: PVE.Utils.render_backup_days_of_week
1084 },
1085 {
1086 header: gettext('Start Time'),
1087 width: 60,
1088 sortable: true,
1089 dataIndex: 'starttime'
1090 },
1091 {
1092 header: gettext('Storage'),
1093 width: 100,
1094 sortable: true,
1095 dataIndex: 'storage'
1096 },
1097 {
1098 header: gettext('Selection'),
1099 flex: 1,
1100 sortable: false,
1101 dataIndex: 'vmid',
1102 renderer: PVE.Utils.render_backup_selection
1103 }
1104 ],
1105 listeners: {
1106 activate: reload,
1107 itemdblclick: run_editor
1108 }
1109 });
1110
1111 me.callParent();
1112 }
1113 }, function() {
1114
1115 Ext.define('pve-cluster-backup', {
1116 extend: 'Ext.data.Model',
1117 fields: [
1118 'id', 'starttime', 'dow',
1119 'storage', 'node', 'vmid', 'exclude',
1120 'mailto', 'pool', 'compress', 'mode',
1121 { name: 'enabled', type: 'boolean' },
1122 { name: 'all', type: 'boolean' }
1123 ]
1124 });
1125 });