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