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