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