]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/dc/Backup.js
ui: not backed: add emptyText to search field
[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 header: {
493 items: [{
494 xtype: 'textfield',
495 fieldLabel: gettext('Search'),
496 labelWidth: 50,
497 emptyText: 'Name, VMID, Type',
498 width: 200,
499 padding: '0 5 0 0',
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) {
521 if (data[property] === null) {
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 }
539 ]},
540 });
541
542 me.callParent();
543
544 me.reload();
545 }
546 });
547
548 Ext.define('PVE.dc.BackupInfo', {
549 extend: 'Proxmox.panel.InputPanel',
550 alias: 'widget.pveBackupInfo',
551
552 padding: '5 0 5 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
701
702 Ext.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 emptyText: 'Name, VMID, Type',
745 enableKeyEvents: true,
746 listeners: {
747 buffer: 500,
748 keyup: function(field) {
749 let searchValue = field.getValue();
750 searchValue = searchValue.toLowerCase();
751
752 me.store.clearFilter(true);
753 me.store.filterBy(function(record) {
754 let match = false;
755
756 Ext.each(['name', 'vmid', 'type'], function(property) {
757 if (record.data[property] == null) {
758 return;
759 }
760
761 let v = record.data[property].toString();
762 if (v !== undefined) {
763 v = v.toLowerCase();
764 if (v.includes(searchValue)) {
765 match = true;
766 return;
767 }
768 }
769 });
770 return match;
771 });
772 }
773 }
774 }
775 ],
776 viewConfig: {
777 stripeRows: true,
778 trackOver: false,
779 },
780 });
781 me.callParent();
782 },
783 });
784
785 Ext.define('PVE.dc.BackupView', {
786 extend: 'Ext.grid.GridPanel',
787
788 alias: ['widget.pveDcBackupView'],
789
790 onlineHelp: 'chapter_vzdump',
791
792 allText: '-- ' + gettext('All') + ' --',
793
794 initComponent : function() {
795 var me = this;
796
797 var store = new Ext.data.Store({
798 model: 'pve-cluster-backup',
799 proxy: {
800 type: 'proxmox',
801 url: "/api2/json/cluster/backup"
802 }
803 });
804
805 var not_backed_store = new Ext.data.Store({
806 sorters: 'vmid',
807 proxy:{
808 type: 'proxmox',
809 url: 'api2/json/cluster/backupinfo/not_backed_up',
810 },
811 });
812
813 var reload = function() {
814 store.load();
815 not_backed_store.load({
816 callback: function(records, operation, success) {
817 if (records.length) {
818 not_backed_warning.setVisible(true);
819 not_backed_btn.setVisible(true);
820 } else {
821 not_backed_warning.setVisible(false);
822 not_backed_btn.setVisible(false);
823 }
824 },
825 });
826 };
827
828 var sm = Ext.create('Ext.selection.RowModel', {});
829
830 var run_editor = function() {
831 var rec = sm.getSelection()[0];
832 if (!rec) {
833 return;
834 }
835
836 var win = Ext.create('PVE.dc.BackupEdit', {
837 jobid: rec.data.id
838 });
839 win.on('destroy', reload);
840 win.show();
841 };
842
843 var run_detail = function() {
844 let me = this;
845 let record = sm.getSelection()[0]
846 if (!record) {
847 return;
848 }
849 let infoview = Ext.create('PVE.dc.BackupInfo', {
850 flex: 0,
851 layout: 'fit',
852 record: record.data,
853 });
854 let disktree = Ext.create('PVE.dc.BackupDiskTree', {
855 title: gettext('Included disks'),
856 flex: 1,
857 jobid: record.data.id,
858 });
859
860 Ext.create('Ext.window.Window', {
861 modal: true,
862 width: 800,
863 height: 600,
864 stateful: true,
865 stateId: 'backup-detail-view',
866 resizable: true,
867 layout: 'fit',
868 title: gettext('Backup Details'),
869
870 items:[{
871 xtype: 'panel',
872 region: 'center',
873 layout: {
874 type: 'vbox',
875 align: 'stretch'
876 },
877 items: [infoview, disktree],
878 }]
879 }).show();
880 };
881
882 var run_backup_now = function(job) {
883 job = Ext.clone(job);
884
885 let jobNode = job.node;
886 // Remove properties related to scheduling
887 delete job.enabled;
888 delete job.starttime;
889 delete job.dow;
890 delete job.id;
891 delete job.node;
892 job.all = job.all === true ? 1 : 0;
893
894 let allNodes = PVE.data.ResourceStore.getNodes();
895 let nodes = allNodes.filter(node => node.status === 'online').map(node => node.node);
896 let errors = [];
897
898 if (jobNode !== undefined) {
899 if (!nodes.includes(jobNode)) {
900 Ext.Msg.alert('Error', "Node '"+ jobNode +"' from backup job isn't online!");
901 return;
902 }
903 nodes = [ jobNode ];
904 } else {
905 let unkownNodes = allNodes.filter(node => node.status !== 'online');
906 if (unkownNodes.length > 0)
907 errors.push(unkownNodes.map(node => node.node + ": " + gettext("Node is offline")));
908 }
909 let jobTotalCount = nodes.length, jobsStarted = 0;
910
911 Ext.Msg.show({
912 title: gettext('Please wait...'),
913 closable: false,
914 progress: true,
915 progressText: '0/' + jobTotalCount,
916 });
917
918 let postRequest = function () {
919 jobsStarted++;
920 Ext.Msg.updateProgress(jobsStarted / jobTotalCount, jobsStarted + '/' + jobTotalCount);
921
922 if (jobsStarted == jobTotalCount) {
923 Ext.Msg.hide();
924 if (errors.length > 0) {
925 Ext.Msg.alert('Error', 'Some errors have been encountered:<br />' + errors.join('<br />'));
926 }
927 }
928 };
929
930 nodes.forEach(node => Proxmox.Utils.API2Request({
931 url: '/nodes/' + node + '/vzdump',
932 method: 'POST',
933 params: job,
934 failure: function (response, opts) {
935 errors.push(node + ': ' + response.htmlStatus);
936 postRequest();
937 },
938 success: postRequest
939 }));
940 };
941
942 var run_show_not_backed = function() {
943 var me = this;
944 var backedinfo = Ext.create('PVE.dc.BackedGuests', {
945 flex: 1,
946 layout: 'fit',
947 store: not_backed_store,
948 });
949
950 var win = Ext.create('Ext.window.Window', {
951 modal: true,
952 width: 600,
953 height: 500,
954 resizable: true,
955 layout: 'fit',
956 title: gettext('Guests without backup job'),
957
958 items:[{
959 xtype: 'panel',
960 region: 'center',
961 layout: {
962 type: 'vbox',
963 align: 'stretch'
964 },
965 items: [backedinfo],
966 }]
967 }).show();
968 };
969
970 var edit_btn = new Proxmox.button.Button({
971 text: gettext('Edit'),
972 disabled: true,
973 selModel: sm,
974 handler: run_editor
975 });
976
977 var run_btn = new Proxmox.button.Button({
978 text: gettext('Run now'),
979 disabled: true,
980 selModel: sm,
981 handler: function() {
982 var rec = sm.getSelection()[0];
983 if (!rec) {
984 return;
985 }
986
987 Ext.Msg.show({
988 title: gettext('Confirm'),
989 icon: Ext.Msg.QUESTION,
990 msg: gettext('Start the selected backup job now?'),
991 buttons: Ext.Msg.YESNO,
992 callback: function(btn) {
993 if (btn !== 'yes') {
994 return;
995 }
996 run_backup_now(rec.data);
997 }
998 });
999 }
1000 });
1001
1002 var remove_btn = Ext.create('Proxmox.button.StdRemoveButton', {
1003 selModel: sm,
1004 baseurl: '/cluster/backup',
1005 callback: function() {
1006 reload();
1007 }
1008 });
1009
1010 var detail_btn = new Proxmox.button.Button({
1011 text: gettext('Job Detail'),
1012 disabled: true,
1013 tooltip: gettext('Show job details and which guests and volumes are affected by the backup job'),
1014 selModel: sm,
1015 handler: run_detail,
1016 });
1017
1018 var not_backed_warning = Ext.create('Ext.toolbar.TextItem', {
1019 html: '<i class="fa fa-fw fa-exclamation-circle"></i>' + gettext('Some guests are not covered by any backup job.'),
1020 hidden: true,
1021 });
1022
1023 var not_backed_btn = new Proxmox.button.Button({
1024 text: gettext('Show'),
1025 hidden: true,
1026 handler: run_show_not_backed,
1027 });
1028
1029 Proxmox.Utils.monStoreErrors(me, store);
1030
1031 Ext.apply(me, {
1032 store: store,
1033 selModel: sm,
1034 stateful: true,
1035 stateId: 'grid-dc-backup',
1036 viewConfig: {
1037 trackOver: false
1038 },
1039 tbar: [
1040 {
1041 text: gettext('Add'),
1042 handler: function() {
1043 var win = Ext.create('PVE.dc.BackupEdit',{});
1044 win.on('destroy', reload);
1045 win.show();
1046 }
1047 },
1048 '-',
1049 remove_btn,
1050 edit_btn,
1051 detail_btn,
1052 '-',
1053 run_btn,
1054 '->',
1055 not_backed_warning,
1056 not_backed_btn,
1057 ],
1058 columns: [
1059 {
1060 header: gettext('Enabled'),
1061 width: 80,
1062 dataIndex: 'enabled',
1063 xtype: 'checkcolumn',
1064 sortable: true,
1065 disabled: true,
1066 disabledCls: 'x-item-enabled',
1067 stopSelection: false
1068 },
1069 {
1070 header: gettext('Node'),
1071 width: 100,
1072 sortable: true,
1073 dataIndex: 'node',
1074 renderer: function(value) {
1075 if (value) {
1076 return value;
1077 }
1078 return me.allText;
1079 }
1080 },
1081 {
1082 header: gettext('Day of week'),
1083 width: 200,
1084 sortable: false,
1085 dataIndex: 'dow',
1086 renderer: PVE.Utils.render_backup_days_of_week
1087 },
1088 {
1089 header: gettext('Start Time'),
1090 width: 60,
1091 sortable: true,
1092 dataIndex: 'starttime'
1093 },
1094 {
1095 header: gettext('Storage'),
1096 width: 100,
1097 sortable: true,
1098 dataIndex: 'storage'
1099 },
1100 {
1101 header: gettext('Selection'),
1102 flex: 1,
1103 sortable: false,
1104 dataIndex: 'vmid',
1105 renderer: PVE.Utils.render_backup_selection
1106 }
1107 ],
1108 listeners: {
1109 activate: reload,
1110 itemdblclick: run_editor
1111 }
1112 });
1113
1114 me.callParent();
1115 }
1116 }, function() {
1117
1118 Ext.define('pve-cluster-backup', {
1119 extend: 'Ext.data.Model',
1120 fields: [
1121 'id', 'starttime', 'dow',
1122 'storage', 'node', 'vmid', 'exclude',
1123 'mailto', 'pool', 'compress', 'mode',
1124 { name: 'enabled', type: 'boolean' },
1125 { name: 'all', type: 'boolean' }
1126 ]
1127 });
1128 });