]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/dc/Backup.js
ui: fix usage of findRecord
[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 ? false : true,
216 value: me.isCreate ? 'always' : ''
217 },
218 {
219 xtype: 'pveCompressionSelector',
220 fieldLabel: gettext('Compression'),
221 name: 'compress',
222 deleteEmpty: me.isCreate ? false : true,
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
319 } else {
320 selPool.setVisible(false);
321 selPool.allowBlank = true;
322 }
323 var list = vmidField.getValue();
324 update_vmid_selection(list, value);
325 });
326
327 var reload = function() {
328 store.load({
329 params: { type: 'vm' },
330 callback: function() {
331 var node = nodesel.getValue();
332 store.clearFilter();
333 store.filterBy(function(rec) {
334 return (!node || node.length === 0 || rec.get('node') === node);
335 });
336 var list = vmidField.getValue();
337 var mode = selModeField.getValue();
338 if (mode === 'all') {
339 sm.selectAll(true);
340 } else if (mode === 'pool'){
341 selectPoolMembers(selPool.value);
342 } else {
343 update_vmid_selection(list, mode);
344 }
345 }
346 });
347 };
348
349 Ext.applyIf(me, {
350 subject: gettext("Backup Job"),
351 url: url,
352 method: method,
353 items: [ ipanel, vmgrid ]
354 });
355
356 me.callParent();
357
358 if (me.isCreate) {
359 selModeField.setValue('include');
360 } else {
361 me.load({
362 success: function(response, options) {
363 var data = response.result.data;
364
365 data.dow = data.dow.split(',');
366
367 if (data.all || data.exclude) {
368 if (data.exclude) {
369 data.vmid = data.exclude;
370 data.selMode = 'exclude';
371 } else {
372 data.vmid = '';
373 data.selMode = 'all';
374 }
375 } else if (data.pool) {
376 data.selMode = 'pool';
377 data.selPool = data.pool;
378 } else {
379 data.selMode = 'include';
380 }
381
382 me.setValues(data);
383 }
384 });
385 }
386
387 reload();
388 }
389 });
390
391
392 Ext.define('PVE.dc.BackupDiskTree', {
393 extend: 'Ext.tree.Panel',
394 alias: 'widget.pveBackupDiskTree',
395
396 folderSort: true,
397 rootVisible: false,
398
399 store: {
400 sorters: 'id',
401 data: {},
402 },
403
404 tools: [
405 {
406 type: 'expand',
407 tooltip: gettext('Expand All'),
408 scope: this,
409 callback: function(panel) {
410 panel.expandAll();
411 },
412 },
413 {
414 type: 'collapse',
415 tooltip: gettext('Collapse All'),
416 scope: this,
417 callback: function(panel) {
418 panel.collapseAll();
419 }
420 },
421 ],
422
423 columns: [
424 {
425 xtype: 'treecolumn',
426 text: gettext('Guest Image'),
427 renderer: function(value, meta, record) {
428 if (record.data.type) {
429 // guest level
430 let ret = value;
431 if (record.data.name) {
432 ret += " (" + record.data.name + ")";
433 }
434 return ret;
435 } else {
436 // volume level
437 // extJS needs unique IDs but we only want to show the
438 // volumes key from "vmid:key"
439 return value.split(':')[1] + " - " + record.data.name;
440 }
441 },
442 dataIndex: 'id',
443 flex: 6,
444 },
445 {
446 text: gettext('Type'),
447 dataIndex: 'type',
448 flex: 1,
449 },
450 {
451 text: gettext('Backup Job'),
452 renderer: PVE.Utils.render_backup_status,
453 dataIndex: 'included',
454 flex: 3,
455 },
456 ],
457
458 reload: function() {
459 var me = this;
460 var sm = me.getSelectionModel();
461
462 Proxmox.Utils.API2Request({
463 url: "/cluster/backup/" + me.jobid + "/included_volumes",
464 waitMsgTarget: me,
465 method: 'GET',
466 failure: function(response, opts) {
467 Proxmox.Utils.setErrorMask(me, response.htmlStatus);
468 },
469 success: function(response, opts) {
470 sm.deselectAll();
471 me.setRootNode(response.result.data);
472 me.expandAll();
473 },
474 });
475 },
476
477 initComponent: function() {
478 var me = this;
479
480 if (!me.jobid) {
481 throw "no job id specified";
482 }
483
484 var sm = Ext.create('Ext.selection.TreeModel', {});
485
486 Ext.apply(me, {
487 selModel: sm,
488 fields: ['id', 'type',
489 {
490 type: 'string',
491 name: 'iconCls',
492 calculate: function(data) {
493 var txt = 'fa x-fa-tree fa-';
494 if (data.leaf && !data.type) {
495 return txt + 'hdd-o';
496 } else if (data.type === 'qemu') {
497 return txt + 'desktop';
498 } else if (data.type === 'lxc') {
499 return txt + 'cube';
500 } else {
501 return txt + 'question-circle';
502 }
503 }
504 }
505 ],
506 header: {
507 items: [{
508 xtype: 'textfield',
509 fieldLabel: gettext('Search'),
510 labelWidth: 50,
511 emptyText: 'Name, VMID, Type',
512 width: 200,
513 padding: '0 5 0 0',
514 enableKeyEvents: true,
515 listeners: {
516 buffer: 500,
517 keyup: function(field) {
518 let searchValue = field.getValue();
519 searchValue = searchValue.toLowerCase();
520
521 me.store.clearFilter(true);
522 me.store.filterBy(function(record) {
523 let match = false;
524
525 let data = '';
526 if (record.data.depth == 0) {
527 return true;
528 } else if (record.data.depth == 1) {
529 data = record.data;
530 } else if (record.data.depth == 2) {
531 data = record.parentNode.data;
532 }
533
534 Ext.each(['name', 'id', 'type'], function(property) {
535 if (data[property] === null) {
536 return;
537 }
538
539 let v = data[property].toString();
540 if (v !== undefined) {
541 v = v.toLowerCase();
542 if (v.includes(searchValue)) {
543 match = true;
544 return;
545 }
546 }
547 });
548 return match;
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
1132 Ext.define('pve-cluster-backup', {
1133 extend: 'Ext.data.Model',
1134 fields: [
1135 'id', 'starttime', 'dow',
1136 'storage', 'node', 'vmid', 'exclude',
1137 'mailto', 'pool', 'compress', 'mode',
1138 { name: 'enabled', type: 'boolean' },
1139 { name: 'all', type: 'boolean' }
1140 ]
1141 });
1142 });