]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/dc/Backup.js
ui: fix backup job create
[pve-manager.git] / www / manager6 / dc / Backup.js
CommitLineData
ad74d86c 1Ext.define('PVE.dc.BackupEdit', {
9fccc702 2 extend: 'Proxmox.window.Edit',
ad74d86c
DM
3 alias: ['widget.pveDcBackupEdit'],
4
d232c409
DC
5 mixins: ['Proxmox.Mixin.CBind'],
6
fc03fcb8
TL
7 defaultFocus: undefined,
8
d232c409
DC
9 subject: gettext("Backup Job"),
10 bodyPadding: 0,
ad74d86c 11
d232c409
DC
12 url: '/api2/extjs/cluster/backup',
13 method: 'POST',
14 isCreate: true,
ad74d86c 15
d232c409
DC
16 cbindData: function() {
17 let me = this;
18 if (me.jobid) {
19 me.isCreate = false;
20 me.method = 'PUT';
21 me.url += `/${me.jobid}`;
12809bba 22 }
d232c409
DC
23 return {};
24 },
12809bba 25
d232c409
DC
26 controller: {
27 xclass: 'Ext.app.ViewController',
28
29 onGetValues: function(values) {
30 let me = this;
31 let isCreate = me.getView().isCreate;
32 if (!values.node) {
33 if (!isCreate) {
34 Proxmox.Utils.assemble_field_data(values, { 'delete': 'node' });
35 }
36 delete values.node;
37 }
38
75601945
LW
39 // Get rid of new-old parameters for notification settings.
40 // These should only be set for those selected few who ran
41 // pve-manager from pvetest.
f2aa317a
DC
42 if (!isCreate) {
43 Proxmox.Utils.assemble_field_data(values, { 'delete': 'notification-policy' });
44 Proxmox.Utils.assemble_field_data(values, { 'delete': 'notification-target' });
45 }
2c4780cc 46
d232c409
DC
47 if (!values.id && isCreate) {
48 values.id = 'backup-' + Ext.data.identifier.Uuid.Global.generate().slice(0, 13);
49 }
50
51 let selMode = values.selMode;
52 delete values.selMode;
53
54 if (selMode === 'all') {
55 values.all = 1;
56 values.exclude = '';
57 delete values.vmid;
58 } else if (selMode === 'exclude') {
59 values.all = 1;
60 values.exclude = values.vmid;
61 delete values.vmid;
62 } else if (selMode === 'pool') {
63 delete values.vmid;
64 }
ad74d86c 65
d232c409
DC
66 if (selMode !== 'pool') {
67 delete values.pool;
68 }
69 return values;
70 },
71
72 nodeChange: function(f, value) {
73 let me = this;
74 me.lookup('storageSelector').setNodename(value);
75 let vmgrid = me.lookup('vmgrid');
76 let store = vmgrid.getStore();
77
78 store.clearFilter();
79 store.filterBy(function(rec) {
80 return !value || rec.get('node') === value;
81 });
ad74d86c 82
d232c409
DC
83 let mode = me.lookup('modeSelector').getValue();
84 if (mode === 'all') {
85 vmgrid.selModel.selectAll(true);
86 }
87 if (mode === 'pool') {
88 me.selectPoolMembers();
89 }
90 },
91
92 storageChange: function(f, v) {
93 let me = this;
94 let rec = f.getStore().findRecord('storage', v, 0, false, true, true);
95 let compressionSelector = me.lookup('compressionSelector');
96
97 if (rec?.data?.type === 'pbs') {
98 compressionSelector.setValue('zstd');
99 compressionSelector.setDisabled(true);
100 } else if (!compressionSelector.getEditable()) {
101 compressionSelector.setDisabled(false);
102 }
103 },
104
105 selectPoolMembers: function() {
106 let me = this;
25f7446c
DC
107 let mode = me.lookup('modeSelector').getValue();
108
109 if (mode !== 'pool') {
110 return;
111 }
112
d232c409
DC
113 let vmgrid = me.lookup('vmgrid');
114 let poolid = me.lookup('poolSelector').getValue();
115
116 vmgrid.getSelectionModel().deselectAll(true);
ab648869
TM
117 if (!poolid) {
118 return;
119 }
e440f8a4 120 vmgrid.getStore().filter([
ab648869
TM
121 {
122 id: 'poolFilter',
123 property: 'pool',
f6710aac
TL
124 value: poolid,
125 },
ab648869 126 ]);
e440f8a4 127 vmgrid.selModel.selectAll(true);
d232c409 128 },
ad74d86c 129
d232c409
DC
130 modeChange: function(f, value, oldValue) {
131 let me = this;
132 let vmgrid = me.lookup('vmgrid');
133 vmgrid.getStore().removeFilter('poolFilter');
ab648869 134
d232c409
DC
135 if (oldValue === 'all' && value !== 'all') {
136 vmgrid.getSelectionModel().deselectAll(true);
ab648869
TM
137 }
138
ad74d86c 139 if (value === 'all') {
d232c409 140 vmgrid.getSelectionModel().selectAll(true);
ad74d86c 141 }
ab648869
TM
142
143 if (value === 'pool') {
d232c409
DC
144 me.selectPoolMembers();
145 }
146 },
147
148 init: function(view) {
149 let me = this;
150 if (view.isCreate) {
151 me.lookup('modeSelector').setValue('include');
ab648869 152 } else {
d232c409
DC
153 view.load({
154 success: function(response, _options) {
155 let data = response.result.data;
156
75601945
LW
157 // Migrate 'new'-old notification-policy back to
158 // old-old mailnotification. Only should affect
159 // users who used pve-manager from pvetest.
160 // This was a remnant of notifications before the
161 // overhaul.
162 let policy = data['notification-policy'];
163 if (policy === 'always' || policy === 'failure') {
164 data.mailnotification = policy;
2c4780cc
LW
165 }
166
d232c409
DC
167 if (data.exclude) {
168 data.vmid = data.exclude;
169 data.selMode = 'exclude';
170 } else if (data.all) {
171 data.vmid = '';
172 data.selMode = 'all';
173 } else if (data.pool) {
174 data.selMode = 'pool';
175 data.selPool = data.pool;
176 } else {
177 data.selMode = 'include';
178 }
179
180 me.getViewModel().set('selMode', data.selMode);
181
182 if (data['prune-backups']) {
183 Object.assign(data, data['prune-backups']);
184 delete data['prune-backups'];
185 } else if (data.maxfiles !== undefined) {
186 if (data.maxfiles > 0) {
187 data['keep-last'] = data.maxfiles;
188 } else {
189 data['keep-all'] = 1;
190 }
191 delete data.maxfiles;
192 }
193
194 if (data['notes-template']) {
195 data['notes-template'] =
196 PVE.Utils.unEscapeNotesTemplate(data['notes-template']);
197 }
198
199 view.setValues(data);
200 },
201 });
ad74d86c 202 }
d232c409
DC
203 },
204 },
60e049c2 205
d232c409
DC
206 viewModel: {
207 data: {
208 selMode: 'include',
209 },
210
211 formulas: {
212 poolMode: (get) => get('selMode') === 'pool',
213 disableVMSelection: (get) => get('selMode') !== 'include' && get('selMode') !== 'exclude',
2c4780cc 214 mailNotificationSelected: (get) => get('notificationMode') === 'mailto',
d232c409
DC
215 },
216 },
217
218 items: [
219 {
220 xtype: 'tabpanel',
221 region: 'center',
222 layout: 'fit',
223 bodyPadding: 10,
759c752c
FE
224 items: [
225 {
d232c409
DC
226 xtype: 'container',
227 title: gettext('General'),
759c752c 228 region: 'center',
d232c409
DC
229 layout: {
230 type: 'vbox',
231 align: 'stretch',
232 },
759c752c 233 items: [
34721757
FE
234 {
235 xtype: 'inputpanel',
d232c409
DC
236 onlineHelp: 'chapter_vzdump',
237 column1: [
238 {
239 xtype: 'pveNodeSelector',
240 name: 'node',
241 fieldLabel: gettext('Node'),
242 allowBlank: true,
243 editable: true,
244 autoSelect: false,
245 emptyText: '-- ' + gettext('All') + ' --',
246 listeners: {
247 change: 'nodeChange',
248 },
249 },
250 {
251 xtype: 'pveStorageSelector',
252 reference: 'storageSelector',
253 fieldLabel: gettext('Storage'),
254 clusterView: true,
255 storageContent: 'backup',
256 allowBlank: false,
257 name: 'storage',
258 listeners: {
259 change: 'storageChange',
260 },
261 },
34721757 262 {
d232c409
DC
263 xtype: 'pveCalendarEvent',
264 fieldLabel: gettext('Schedule'),
265 allowBlank: false,
266 name: 'schedule',
34721757
FE
267 },
268 {
d232c409
DC
269 xtype: 'proxmoxKVComboBox',
270 reference: 'modeSelector',
271 comboItems: [
272 ['include', gettext('Include selected VMs')],
273 ['all', gettext('All')],
274 ['exclude', gettext('Exclude selected VMs')],
275 ['pool', gettext('Pool based')],
276 ],
277 fieldLabel: gettext('Selection mode'),
278 name: 'selMode',
279 value: '',
280 bind: {
281 value: '{selMode}',
282 },
283 listeners: {
284 change: 'modeChange',
285 },
286 },
287 {
288 xtype: 'pvePoolSelector',
289 reference: 'poolSelector',
290 fieldLabel: gettext('Pool to backup'),
291 hidden: true,
292 allowBlank: false,
293 name: 'pool',
294 listeners: {
295 change: 'selectPoolMembers',
296 },
297 bind: {
298 hidden: '{!poolMode}',
299 disabled: '{!poolMode}',
34721757 300 },
34721757
FE
301 },
302 ],
d232c409 303 column2: [
d232c409
DC
304 {
305 xtype: 'pveEmailNotificationSelector',
2c4780cc 306 fieldLabel: gettext('Notify'),
75601945 307 name: 'mailnotification',
d232c409
DC
308 cbind: {
309 value: (get) => get('isCreate') ? 'always' : '',
310 deleteEmpty: '{!isCreate}',
311 },
312 },
2c4780cc
LW
313 {
314 xtype: 'textfield',
315 fieldLabel: gettext('Send email to'),
316 name: 'mailto',
2c4780cc 317 },
d232c409 318 {
5265a2d1 319 xtype: 'pveBackupCompressionSelector',
d232c409
DC
320 reference: 'compressionSelector',
321 fieldLabel: gettext('Compression'),
322 name: 'compress',
323 cbind: {
324 deleteEmpty: '{!isCreate}',
325 },
326 value: 'zstd',
327 },
328 {
329 xtype: 'pveBackupModeSelector',
330 fieldLabel: gettext('Mode'),
331 value: 'snapshot',
332 name: 'mode',
333 },
334 {
335 xtype: 'proxmoxcheckbox',
336 fieldLabel: gettext('Enable'),
337 name: 'enabled',
338 uncheckedValue: 0,
339 defaultValue: 1,
340 checked: true,
341 },
342 ],
343 columnB: [
344 {
345 xtype: 'proxmoxtextfield',
346 name: 'comment',
347 fieldLabel: gettext('Job Comment'),
348 cbind: {
349 deleteEmpty: '{!isCreate}',
350 },
351 autoEl: {
352 tag: 'div',
353 'data-qtip': gettext('Description of the job'),
354 },
355 },
356 {
357 xtype: 'vmselector',
358 reference: 'vmgrid',
359 height: 300,
360 name: 'vmid',
361 disabled: true,
362 allowBlank: false,
363 columnSelection: ['vmid', 'node', 'status', 'name', 'type'],
364 bind: {
365 disabled: '{disableVMSelection}',
366 },
367 },
368 ],
369 advancedColumn1: [
370 {
371 xtype: 'proxmoxcheckbox',
372 fieldLabel: gettext('Repeat missed'),
373 name: 'repeat-missed',
374 uncheckedValue: 0,
375 defaultValue: 0,
376 cbind: {
377 deleteDefaultValue: '{!isCreate}',
378 },
379 },
380 ],
381 onGetValues: function(values) {
382 return this.up('window').getController().onGetValues(values);
383 },
34721757 384 },
759c752c
FE
385 ],
386 },
d232c409
DC
387 {
388 xtype: 'pveBackupJobPrunePanel',
389 title: gettext('Retention'),
390 cbind: {
391 isCreate: '{isCreate}',
392 },
393 keepAllDefaultForCreate: false,
394 showPBSHint: false,
395 fallbackHintHtml: gettext('Without any keep option, the storage\'s configuration or node\'s vzdump.conf is used as fallback'),
396 },
397 {
398 xtype: 'inputpanel',
399 title: gettext('Note Template'),
400 region: 'center',
401 layout: {
402 type: 'vbox',
403 align: 'stretch',
404 },
405 onGetValues: function(values) {
406 if (values['notes-template']) {
407 values['notes-template'] =
408 PVE.Utils.escapeNotesTemplate(values['notes-template']);
759c752c 409 }
d232c409
DC
410 return values;
411 },
412 items: [
413 {
414 xtype: 'textarea',
415 name: 'notes-template',
416 fieldLabel: gettext('Backup Notes'),
417 height: 100,
418 maxLength: 512,
419 cbind: {
420 deleteEmpty: '{!isCreate}',
421 value: (get) => get('isCreate') ? '{{guestname}}' : undefined,
422 },
423 },
424 {
425 xtype: 'box',
426 style: {
427 margin: '8px 0px',
428 'line-height': '1.5em',
429 },
430 html: gettext('The notes are added to each backup created by this job.')
431 + '<br>'
432 + Ext.String.format(
433 gettext('Possible template variables are: {0}'),
434 PVE.Utils.notesTemplateVars.map(v => `<code>{{${v}}}</code>`).join(', '),
435 ),
436 },
437 ],
12809bba 438 },
d232c409
DC
439 ],
440 },
441 ],
ad74d86c
DM
442});
443
ad74d86c
DM
444Ext.define('PVE.dc.BackupView', {
445 extend: 'Ext.grid.GridPanel',
446
447 alias: ['widget.pveDcBackupView'],
448
ba93a9c6
DC
449 onlineHelp: 'chapter_vzdump',
450
ad74d86c 451 allText: '-- ' + gettext('All') + ' --',
ad74d86c 452
8058410f 453 initComponent: function() {
12809bba 454 let me = this;
ad74d86c 455
12809bba 456 let store = new Ext.data.Store({
ad74d86c
DM
457 model: 'pve-cluster-backup',
458 proxy: {
12809bba 459 type: 'proxmox',
f6710aac
TL
460 url: "/api2/json/cluster/backup",
461 },
ad74d86c
DM
462 });
463
12809bba 464 let not_backed_store = new Ext.data.Store({
7d2fac4a 465 sorters: 'vmid',
8058410f 466 proxy: {
7d2fac4a 467 type: 'proxmox',
1a2e0e23 468 url: 'api2/json/cluster/backup-info/not-backed-up',
7d2fac4a
AL
469 },
470 });
471
315fecea 472 let noBackupJobInfoButton;
12809bba 473 let reload = function() {
ad74d86c 474 store.load();
7d2fac4a 475 not_backed_store.load({
315fecea 476 callback: records => noBackupJobInfoButton.setVisible(records.length > 0),
7d2fac4a 477 });
ad74d86c
DM
478 };
479
12809bba 480 let sm = Ext.create('Ext.selection.RowModel', {});
ad74d86c 481
12809bba
TL
482 let run_editor = function() {
483 let rec = sm.getSelection()[0];
ad74d86c
DM
484 if (!rec) {
485 return;
486 }
487
12809bba 488 let win = Ext.create('PVE.dc.BackupEdit', {
f6710aac 489 jobid: rec.data.id,
43b2494b
SR
490 });
491 win.on('destroy', reload);
492 win.show();
ad74d86c
DM
493 };
494
12809bba 495 let run_detail = function() {
8058410f 496 let record = sm.getSelection()[0];
01ad47af
AL
497 if (!record) {
498 return;
499 }
386c9ce5 500 Ext.create('Ext.window.Window', {
01ad47af 501 modal: true,
386c9ce5 502 width: 800,
a4240b80 503 height: Ext.getBody().getViewSize().height > 1000 ? 800 : 600, // factor out as common infra?
01ad47af
AL
504 resizable: true,
505 layout: 'fit',
506 title: gettext('Backup Details'),
12809bba
TL
507 items: [
508 {
509 xtype: 'panel',
510 region: 'center',
511 layout: {
512 type: 'vbox',
513 align: 'stretch',
514 },
515 items: [
516 {
517 xtype: 'pveBackupInfo',
518 flex: 0,
519 layout: 'fit',
520 record: record.data,
521 },
522 {
523 xtype: 'pveBackupDiskTree',
524 title: gettext('Included disks'),
525 flex: 1,
526 jobid: record.data.id,
527 },
528 ],
01ad47af 529 },
12809bba 530 ],
01ad47af
AL
531 }).show();
532 };
533
12809bba 534 let run_backup_now = function(job) {
389d3cf1
SR
535 job = Ext.clone(job);
536
a0fecb88 537 let jobNode = job.node;
389d3cf1
SR
538 // Remove properties related to scheduling
539 delete job.enabled;
540 delete job.starttime;
541 delete job.dow;
542 delete job.id;
20d15804
DC
543 delete job.schedule;
544 delete job.type;
389d3cf1 545 delete job.node;
1a8bd901 546 delete job.comment;
31de6834 547 delete job['next-run'];
9cd2e638 548 delete job['repeat-missed'];
389d3cf1
SR
549 job.all = job.all === true ? 1 : 0;
550
93880785
FE
551 ['performance', 'prune-backups'].forEach(key => {
552 if (job[key]) {
553 job[key] = PVE.Parser.printPropertyString(job[key]);
554 }
555 });
3e70f3c5 556
a0fecb88
TL
557 let allNodes = PVE.data.ResourceStore.getNodes();
558 let nodes = allNodes.filter(node => node.status === 'online').map(node => node.node);
559 let errors = [];
560
561 if (jobNode !== undefined) {
562 if (!nodes.includes(jobNode)) {
563 Ext.Msg.alert('Error', "Node '"+ jobNode +"' from backup job isn't online!");
564 return;
565 }
8058410f 566 nodes = [jobNode];
a0fecb88
TL
567 } else {
568 let unkownNodes = allNodes.filter(node => node.status !== 'online');
12809bba 569 if (unkownNodes.length > 0) {errors.push(unkownNodes.map(node => node.node + ": " + gettext("Node is offline")));}
a0fecb88
TL
570 }
571 let jobTotalCount = nodes.length, jobsStarted = 0;
389d3cf1
SR
572
573 Ext.Msg.show({
574 title: gettext('Please wait...'),
575 closable: false,
a0fecb88
TL
576 progress: true,
577 progressText: '0/' + jobTotalCount,
389d3cf1 578 });
389d3cf1 579
8058410f 580 let postRequest = function() {
a0fecb88
TL
581 jobsStarted++;
582 Ext.Msg.updateProgress(jobsStarted / jobTotalCount, jobsStarted + '/' + jobTotalCount);
389d3cf1 583
12809bba 584 if (jobsStarted === jobTotalCount) {
389d3cf1 585 Ext.Msg.hide();
a0fecb88
TL
586 if (errors.length > 0) {
587 Ext.Msg.alert('Error', 'Some errors have been encountered:<br />' + errors.join('<br />'));
389d3cf1
SR
588 }
589 }
a0fecb88
TL
590 };
591
592 nodes.forEach(node => Proxmox.Utils.API2Request({
593 url: '/nodes/' + node + '/vzdump',
594 method: 'POST',
595 params: job,
8058410f 596 failure: function(response, opts) {
a0fecb88
TL
597 errors.push(node + ': ' + response.htmlStatus);
598 postRequest();
599 },
f6710aac 600 success: postRequest,
a0fecb88
TL
601 }));
602 };
389d3cf1 603
5720fafa 604 var edit_btn = new Proxmox.button.Button({
ad74d86c
DM
605 text: gettext('Edit'),
606 disabled: true,
607 selModel: sm,
f6710aac 608 handler: run_editor,
ad74d86c
DM
609 });
610
389d3cf1
SR
611 var run_btn = new Proxmox.button.Button({
612 text: gettext('Run now'),
613 disabled: true,
614 selModel: sm,
615 handler: function() {
616 var rec = sm.getSelection()[0];
617 if (!rec) {
618 return;
619 }
620
621 Ext.Msg.show({
622 title: gettext('Confirm'),
623 icon: Ext.Msg.QUESTION,
624 msg: gettext('Start the selected backup job now?'),
625 buttons: Ext.Msg.YESNO,
626 callback: function(btn) {
627 if (btn !== 'yes') {
628 return;
629 }
630 run_backup_now(rec.data);
f6710aac 631 },
389d3cf1 632 });
f6710aac 633 },
389d3cf1
SR
634 });
635
3b1ca3ff 636 var remove_btn = Ext.create('Proxmox.button.StdRemoveButton', {
ad74d86c 637 selModel: sm,
3b1ca3ff
DC
638 baseurl: '/cluster/backup',
639 callback: function() {
640 reload();
f6710aac 641 },
ad74d86c
DM
642 });
643
01ad47af 644 var detail_btn = new Proxmox.button.Button({
393b74ff 645 text: gettext('Job Detail'),
01ad47af
AL
646 disabled: true,
647 tooltip: gettext('Show job details and which guests and volumes are affected by the backup job'),
648 selModel: sm,
649 handler: run_detail,
650 });
651
12809bba 652 noBackupJobInfoButton = new Proxmox.button.Button({
315fecea
TL
653 text: `${gettext('Show')}: ${gettext('Guests Without Backup Job')}`,
654 tooltip: gettext('Some guests are not covered by any backup job.'),
655 iconCls: 'fa fa-fw fa-exclamation-circle',
7d2fac4a 656 hidden: true,
38e79438
TL
657 handler: () => {
658 Ext.create('Ext.window.Window', {
659 autoShow: true,
660 modal: true,
661 width: 600,
662 height: 500,
663 resizable: true,
664 layout: 'fit',
665 title: gettext('Guests Without Backup Job'),
666 items: [
667 {
668 xtype: 'panel',
669 region: 'center',
670 layout: {
671 type: 'vbox',
672 align: 'stretch',
673 },
674 items: [
675 {
676 xtype: 'pveBackedGuests',
677 flex: 1,
678 layout: 'fit',
679 store: not_backed_store,
680 },
681 ],
682 },
683 ],
684 });
685 },
7d2fac4a
AL
686 });
687
e7ade592 688 Proxmox.Utils.monStoreErrors(me, store);
ad74d86c
DM
689
690 Ext.apply(me, {
691 store: store,
692 selModel: sm,
c4bb9405
DC
693 stateful: true,
694 stateId: 'grid-dc-backup',
ad74d86c 695 viewConfig: {
f6710aac 696 trackOver: false,
ad74d86c 697 },
cc911f63
DC
698 dockedItems: [{
699 xtype: 'toolbar',
700 overflowHandler: 'scroller',
701 dock: 'top',
702 items: [
703 {
704 text: gettext('Add'),
705 handler: function() {
706 var win = Ext.create('PVE.dc.BackupEdit', {});
707 win.on('destroy', reload);
708 win.show();
709 },
f6710aac 710 },
cc911f63
DC
711 '-',
712 remove_btn,
713 edit_btn,
714 detail_btn,
715 '-',
716 run_btn,
717 '->',
718 noBackupJobInfoButton,
719 '-',
720 {
721 xtype: 'proxmoxButton',
722 selModel: null,
723 text: gettext('Schedule Simulator'),
724 handler: () => {
725 let record = sm.getSelection()[0];
726 let schedule;
727 if (record) {
728 schedule = record.data.schedule;
729 }
730 Ext.create('PVE.window.ScheduleSimulator', {
731 autoShow: true,
732 schedule,
733 });
734 },
4d6215bc 735 },
cc911f63
DC
736 ],
737 }],
ad74d86c 738 columns: [
cfdc7ada
EK
739 {
740 header: gettext('Enabled'),
ae9e2161 741 width: 80,
cfdc7ada 742 dataIndex: 'enabled',
b98ffc0d 743 align: 'center',
bacb4173 744 renderer: Proxmox.Utils.renderEnabledIcon,
cfdc7ada
EK
745 sortable: true,
746 },
20d15804
DC
747 {
748 header: gettext('ID'),
749 dataIndex: 'id',
d9e05d34 750 hidden: true,
20d15804 751 },
ad74d86c
DM
752 {
753 header: gettext('Node'),
754 width: 100,
755 sortable: true,
756 dataIndex: 'node',
757 renderer: function(value) {
758 if (value) {
759 return value;
760 }
761 return me.allText;
f6710aac 762 },
ad74d86c
DM
763 },
764 {
20d15804
DC
765 header: gettext('Schedule'),
766 width: 150,
767 dataIndex: 'schedule',
ad74d86c 768 },
8aeb7146
TL
769 {
770 text: gettext('Next Run'),
771 dataIndex: 'next-run',
772 width: 150,
773 renderer: PVE.Utils.render_next_event,
774 },
ad74d86c
DM
775 {
776 header: gettext('Storage'),
777 width: 100,
778 sortable: true,
f6710aac 779 dataIndex: 'storage',
ad74d86c
DM
780 },
781 {
d9e05d34
DC
782 header: gettext('Comment'),
783 dataIndex: 'comment',
784 renderer: Ext.htmlEncode,
611ef475 785 sorter: (a, b) => (a.data.comment || '').localeCompare(b.data.comment || ''),
ad74d86c 786 flex: 1,
d9e05d34
DC
787 },
788 {
fcb267e3
TL
789 header: gettext('Retention'),
790 dataIndex: 'prune-backups',
791 renderer: v => v ? PVE.Parser.printPropertyString(v) : gettext('Fallback from storage config'),
d9e05d34 792 flex: 2,
fcb267e3
TL
793 },
794 {
795 header: gettext('Selection'),
796 flex: 4,
ad74d86c
DM
797 sortable: false,
798 dataIndex: 'vmid',
f6710aac
TL
799 renderer: PVE.Utils.render_backup_selection,
800 },
ad74d86c
DM
801 ],
802 listeners: {
c0b3df6e 803 activate: reload,
f6710aac
TL
804 itemdblclick: run_editor,
805 },
ad74d86c 806 });
60e049c2 807
ad74d86c 808 me.callParent();
f6710aac 809 },
ad74d86c 810}, function() {
ad74d86c
DM
811 Ext.define('pve-cluster-backup', {
812 extend: 'Ext.data.Model',
60e049c2 813 fields: [
12809bba
TL
814 'id',
815 'compress',
816 'dow',
817 'exclude',
818 'mailto',
819 'mode',
820 'node',
821 'pool',
c6801352 822 'prune-backups',
12809bba
TL
823 'starttime',
824 'storage',
825 'vmid',
cfdc7ada 826 { name: 'enabled', type: 'boolean' },
f6710aac
TL
827 { name: 'all', type: 'boolean' },
828 ],
ad74d86c 829 });
cfdc7ada 830});