]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/dc/Backup.js
guest import: allow setting VLAN-tag
[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',
66b20862 209 notificationMode: '__default__',
d232c409
DC
210 },
211
212 formulas: {
213 poolMode: (get) => get('selMode') === 'pool',
214 disableVMSelection: (get) => get('selMode') !== 'include' && get('selMode') !== 'exclude',
66b20862
LW
215 showMailtoFields: (get) =>
216 ['auto', 'legacy-sendmail', '__default__'].includes(get('notificationMode')),
d232c409
DC
217 },
218 },
219
220 items: [
221 {
222 xtype: 'tabpanel',
223 region: 'center',
224 layout: 'fit',
225 bodyPadding: 10,
759c752c
FE
226 items: [
227 {
d232c409
DC
228 xtype: 'container',
229 title: gettext('General'),
759c752c 230 region: 'center',
d232c409
DC
231 layout: {
232 type: 'vbox',
233 align: 'stretch',
234 },
759c752c 235 items: [
34721757
FE
236 {
237 xtype: 'inputpanel',
d232c409
DC
238 onlineHelp: 'chapter_vzdump',
239 column1: [
240 {
241 xtype: 'pveNodeSelector',
242 name: 'node',
243 fieldLabel: gettext('Node'),
244 allowBlank: true,
245 editable: true,
246 autoSelect: false,
247 emptyText: '-- ' + gettext('All') + ' --',
248 listeners: {
249 change: 'nodeChange',
250 },
251 },
252 {
253 xtype: 'pveStorageSelector',
254 reference: 'storageSelector',
255 fieldLabel: gettext('Storage'),
256 clusterView: true,
257 storageContent: 'backup',
258 allowBlank: false,
259 name: 'storage',
260 listeners: {
261 change: 'storageChange',
262 },
263 },
34721757 264 {
d232c409
DC
265 xtype: 'pveCalendarEvent',
266 fieldLabel: gettext('Schedule'),
267 allowBlank: false,
268 name: 'schedule',
34721757
FE
269 },
270 {
d232c409
DC
271 xtype: 'proxmoxKVComboBox',
272 reference: 'modeSelector',
273 comboItems: [
274 ['include', gettext('Include selected VMs')],
275 ['all', gettext('All')],
276 ['exclude', gettext('Exclude selected VMs')],
277 ['pool', gettext('Pool based')],
278 ],
279 fieldLabel: gettext('Selection mode'),
280 name: 'selMode',
281 value: '',
282 bind: {
283 value: '{selMode}',
284 },
285 listeners: {
286 change: 'modeChange',
287 },
288 },
289 {
290 xtype: 'pvePoolSelector',
291 reference: 'poolSelector',
292 fieldLabel: gettext('Pool to backup'),
293 hidden: true,
294 allowBlank: false,
295 name: 'pool',
296 listeners: {
297 change: 'selectPoolMembers',
298 },
299 bind: {
300 hidden: '{!poolMode}',
301 disabled: '{!poolMode}',
34721757 302 },
34721757
FE
303 },
304 ],
d232c409 305 column2: [
66b20862
LW
306 {
307 xtype: 'proxmoxKVComboBox',
308 comboItems: [
309 [
310 '__default__',
311 Ext.String.format(
312 gettext('{0} (Auto)'), Proxmox.Utils.defaultText,
313 ),
314 ],
315 ['auto', gettext('Auto')],
316 ['legacy-sendmail', gettext('Email (legacy)')],
317 ['notification-system', gettext('Notification system')],
318 ],
319 fieldLabel: gettext('Notification mode'),
320 name: 'notification-mode',
321 cbind: {
322 deleteEmpty: '{!isCreate}',
323 },
324 bind: {
325 value: '{notificationMode}',
326 },
327 },
d232c409
DC
328 {
329 xtype: 'pveEmailNotificationSelector',
fb5b8179 330 fieldLabel: gettext('Send email'),
75601945 331 name: 'mailnotification',
d232c409
DC
332 cbind: {
333 value: (get) => get('isCreate') ? 'always' : '',
334 deleteEmpty: '{!isCreate}',
335 },
66b20862
LW
336 bind: {
337 disabled: '{!showMailtoFields}',
338 },
d232c409 339 },
2c4780cc
LW
340 {
341 xtype: 'textfield',
342 fieldLabel: gettext('Send email to'),
343 name: 'mailto',
66b20862
LW
344 bind: {
345 disabled: '{!showMailtoFields}',
346 },
2c4780cc 347 },
d232c409 348 {
5265a2d1 349 xtype: 'pveBackupCompressionSelector',
d232c409
DC
350 reference: 'compressionSelector',
351 fieldLabel: gettext('Compression'),
352 name: 'compress',
353 cbind: {
354 deleteEmpty: '{!isCreate}',
355 },
356 value: 'zstd',
357 },
358 {
359 xtype: 'pveBackupModeSelector',
360 fieldLabel: gettext('Mode'),
361 value: 'snapshot',
362 name: 'mode',
363 },
364 {
365 xtype: 'proxmoxcheckbox',
366 fieldLabel: gettext('Enable'),
367 name: 'enabled',
368 uncheckedValue: 0,
369 defaultValue: 1,
370 checked: true,
371 },
372 ],
373 columnB: [
374 {
375 xtype: 'proxmoxtextfield',
376 name: 'comment',
377 fieldLabel: gettext('Job Comment'),
378 cbind: {
379 deleteEmpty: '{!isCreate}',
380 },
381 autoEl: {
382 tag: 'div',
383 'data-qtip': gettext('Description of the job'),
384 },
385 },
386 {
387 xtype: 'vmselector',
388 reference: 'vmgrid',
389 height: 300,
390 name: 'vmid',
391 disabled: true,
392 allowBlank: false,
393 columnSelection: ['vmid', 'node', 'status', 'name', 'type'],
394 bind: {
395 disabled: '{disableVMSelection}',
396 },
397 },
398 ],
399 advancedColumn1: [
400 {
401 xtype: 'proxmoxcheckbox',
402 fieldLabel: gettext('Repeat missed'),
403 name: 'repeat-missed',
404 uncheckedValue: 0,
405 defaultValue: 0,
406 cbind: {
407 deleteDefaultValue: '{!isCreate}',
408 },
409 },
410 ],
411 onGetValues: function(values) {
412 return this.up('window').getController().onGetValues(values);
413 },
34721757 414 },
759c752c
FE
415 ],
416 },
d232c409
DC
417 {
418 xtype: 'pveBackupJobPrunePanel',
419 title: gettext('Retention'),
420 cbind: {
421 isCreate: '{isCreate}',
422 },
423 keepAllDefaultForCreate: false,
424 showPBSHint: false,
425 fallbackHintHtml: gettext('Without any keep option, the storage\'s configuration or node\'s vzdump.conf is used as fallback'),
426 },
427 {
428 xtype: 'inputpanel',
429 title: gettext('Note Template'),
430 region: 'center',
431 layout: {
432 type: 'vbox',
433 align: 'stretch',
434 },
435 onGetValues: function(values) {
436 if (values['notes-template']) {
437 values['notes-template'] =
438 PVE.Utils.escapeNotesTemplate(values['notes-template']);
759c752c 439 }
d232c409
DC
440 return values;
441 },
442 items: [
443 {
444 xtype: 'textarea',
445 name: 'notes-template',
446 fieldLabel: gettext('Backup Notes'),
447 height: 100,
448 maxLength: 512,
449 cbind: {
450 deleteEmpty: '{!isCreate}',
451 value: (get) => get('isCreate') ? '{{guestname}}' : undefined,
452 },
453 },
454 {
455 xtype: 'box',
456 style: {
457 margin: '8px 0px',
458 'line-height': '1.5em',
459 },
460 html: gettext('The notes are added to each backup created by this job.')
461 + '<br>'
462 + Ext.String.format(
463 gettext('Possible template variables are: {0}'),
464 PVE.Utils.notesTemplateVars.map(v => `<code>{{${v}}}</code>`).join(', '),
465 ),
466 },
467 ],
12809bba 468 },
d232c409
DC
469 ],
470 },
471 ],
ad74d86c
DM
472});
473
ad74d86c
DM
474Ext.define('PVE.dc.BackupView', {
475 extend: 'Ext.grid.GridPanel',
476
477 alias: ['widget.pveDcBackupView'],
478
ba93a9c6
DC
479 onlineHelp: 'chapter_vzdump',
480
ad74d86c 481 allText: '-- ' + gettext('All') + ' --',
ad74d86c 482
8058410f 483 initComponent: function() {
12809bba 484 let me = this;
ad74d86c 485
12809bba 486 let store = new Ext.data.Store({
ad74d86c
DM
487 model: 'pve-cluster-backup',
488 proxy: {
12809bba 489 type: 'proxmox',
f6710aac
TL
490 url: "/api2/json/cluster/backup",
491 },
ad74d86c
DM
492 });
493
12809bba 494 let not_backed_store = new Ext.data.Store({
7d2fac4a 495 sorters: 'vmid',
8058410f 496 proxy: {
7d2fac4a 497 type: 'proxmox',
1a2e0e23 498 url: 'api2/json/cluster/backup-info/not-backed-up',
7d2fac4a
AL
499 },
500 });
501
315fecea 502 let noBackupJobInfoButton;
12809bba 503 let reload = function() {
ad74d86c 504 store.load();
7d2fac4a 505 not_backed_store.load({
315fecea 506 callback: records => noBackupJobInfoButton.setVisible(records.length > 0),
7d2fac4a 507 });
ad74d86c
DM
508 };
509
12809bba 510 let sm = Ext.create('Ext.selection.RowModel', {});
ad74d86c 511
12809bba
TL
512 let run_editor = function() {
513 let rec = sm.getSelection()[0];
ad74d86c
DM
514 if (!rec) {
515 return;
516 }
517
12809bba 518 let win = Ext.create('PVE.dc.BackupEdit', {
f6710aac 519 jobid: rec.data.id,
43b2494b
SR
520 });
521 win.on('destroy', reload);
522 win.show();
ad74d86c
DM
523 };
524
12809bba 525 let run_detail = function() {
8058410f 526 let record = sm.getSelection()[0];
01ad47af
AL
527 if (!record) {
528 return;
529 }
386c9ce5 530 Ext.create('Ext.window.Window', {
01ad47af 531 modal: true,
386c9ce5 532 width: 800,
a4240b80 533 height: Ext.getBody().getViewSize().height > 1000 ? 800 : 600, // factor out as common infra?
01ad47af
AL
534 resizable: true,
535 layout: 'fit',
536 title: gettext('Backup Details'),
12809bba
TL
537 items: [
538 {
539 xtype: 'panel',
540 region: 'center',
541 layout: {
542 type: 'vbox',
543 align: 'stretch',
544 },
545 items: [
546 {
547 xtype: 'pveBackupInfo',
548 flex: 0,
549 layout: 'fit',
550 record: record.data,
551 },
552 {
553 xtype: 'pveBackupDiskTree',
554 title: gettext('Included disks'),
555 flex: 1,
556 jobid: record.data.id,
557 },
558 ],
01ad47af 559 },
12809bba 560 ],
01ad47af
AL
561 }).show();
562 };
563
12809bba 564 let run_backup_now = function(job) {
389d3cf1
SR
565 job = Ext.clone(job);
566
a0fecb88 567 let jobNode = job.node;
389d3cf1
SR
568 // Remove properties related to scheduling
569 delete job.enabled;
570 delete job.starttime;
571 delete job.dow;
572 delete job.id;
20d15804
DC
573 delete job.schedule;
574 delete job.type;
389d3cf1 575 delete job.node;
1a8bd901 576 delete job.comment;
31de6834 577 delete job['next-run'];
9cd2e638 578 delete job['repeat-missed'];
389d3cf1
SR
579 job.all = job.all === true ? 1 : 0;
580
93880785
FE
581 ['performance', 'prune-backups'].forEach(key => {
582 if (job[key]) {
583 job[key] = PVE.Parser.printPropertyString(job[key]);
584 }
585 });
3e70f3c5 586
a0fecb88
TL
587 let allNodes = PVE.data.ResourceStore.getNodes();
588 let nodes = allNodes.filter(node => node.status === 'online').map(node => node.node);
589 let errors = [];
590
591 if (jobNode !== undefined) {
592 if (!nodes.includes(jobNode)) {
593 Ext.Msg.alert('Error', "Node '"+ jobNode +"' from backup job isn't online!");
594 return;
595 }
8058410f 596 nodes = [jobNode];
a0fecb88
TL
597 } else {
598 let unkownNodes = allNodes.filter(node => node.status !== 'online');
12809bba 599 if (unkownNodes.length > 0) {errors.push(unkownNodes.map(node => node.node + ": " + gettext("Node is offline")));}
a0fecb88
TL
600 }
601 let jobTotalCount = nodes.length, jobsStarted = 0;
389d3cf1
SR
602
603 Ext.Msg.show({
604 title: gettext('Please wait...'),
605 closable: false,
a0fecb88
TL
606 progress: true,
607 progressText: '0/' + jobTotalCount,
389d3cf1 608 });
389d3cf1 609
8058410f 610 let postRequest = function() {
a0fecb88
TL
611 jobsStarted++;
612 Ext.Msg.updateProgress(jobsStarted / jobTotalCount, jobsStarted + '/' + jobTotalCount);
389d3cf1 613
12809bba 614 if (jobsStarted === jobTotalCount) {
389d3cf1 615 Ext.Msg.hide();
a0fecb88
TL
616 if (errors.length > 0) {
617 Ext.Msg.alert('Error', 'Some errors have been encountered:<br />' + errors.join('<br />'));
389d3cf1
SR
618 }
619 }
a0fecb88
TL
620 };
621
622 nodes.forEach(node => Proxmox.Utils.API2Request({
623 url: '/nodes/' + node + '/vzdump',
624 method: 'POST',
625 params: job,
8058410f 626 failure: function(response, opts) {
a0fecb88
TL
627 errors.push(node + ': ' + response.htmlStatus);
628 postRequest();
629 },
f6710aac 630 success: postRequest,
a0fecb88
TL
631 }));
632 };
389d3cf1 633
5720fafa 634 var edit_btn = new Proxmox.button.Button({
ad74d86c
DM
635 text: gettext('Edit'),
636 disabled: true,
637 selModel: sm,
f6710aac 638 handler: run_editor,
ad74d86c
DM
639 });
640
389d3cf1
SR
641 var run_btn = new Proxmox.button.Button({
642 text: gettext('Run now'),
643 disabled: true,
644 selModel: sm,
645 handler: function() {
646 var rec = sm.getSelection()[0];
647 if (!rec) {
648 return;
649 }
650
651 Ext.Msg.show({
652 title: gettext('Confirm'),
653 icon: Ext.Msg.QUESTION,
654 msg: gettext('Start the selected backup job now?'),
655 buttons: Ext.Msg.YESNO,
656 callback: function(btn) {
657 if (btn !== 'yes') {
658 return;
659 }
660 run_backup_now(rec.data);
f6710aac 661 },
389d3cf1 662 });
f6710aac 663 },
389d3cf1
SR
664 });
665
3b1ca3ff 666 var remove_btn = Ext.create('Proxmox.button.StdRemoveButton', {
ad74d86c 667 selModel: sm,
3b1ca3ff
DC
668 baseurl: '/cluster/backup',
669 callback: function() {
670 reload();
f6710aac 671 },
ad74d86c
DM
672 });
673
01ad47af 674 var detail_btn = new Proxmox.button.Button({
393b74ff 675 text: gettext('Job Detail'),
01ad47af
AL
676 disabled: true,
677 tooltip: gettext('Show job details and which guests and volumes are affected by the backup job'),
678 selModel: sm,
679 handler: run_detail,
680 });
681
12809bba 682 noBackupJobInfoButton = new Proxmox.button.Button({
315fecea
TL
683 text: `${gettext('Show')}: ${gettext('Guests Without Backup Job')}`,
684 tooltip: gettext('Some guests are not covered by any backup job.'),
685 iconCls: 'fa fa-fw fa-exclamation-circle',
7d2fac4a 686 hidden: true,
38e79438
TL
687 handler: () => {
688 Ext.create('Ext.window.Window', {
689 autoShow: true,
690 modal: true,
691 width: 600,
692 height: 500,
693 resizable: true,
694 layout: 'fit',
695 title: gettext('Guests Without Backup Job'),
696 items: [
697 {
698 xtype: 'panel',
699 region: 'center',
700 layout: {
701 type: 'vbox',
702 align: 'stretch',
703 },
704 items: [
705 {
706 xtype: 'pveBackedGuests',
707 flex: 1,
708 layout: 'fit',
709 store: not_backed_store,
710 },
711 ],
712 },
713 ],
714 });
715 },
7d2fac4a
AL
716 });
717
e7ade592 718 Proxmox.Utils.monStoreErrors(me, store);
ad74d86c
DM
719
720 Ext.apply(me, {
721 store: store,
722 selModel: sm,
c4bb9405
DC
723 stateful: true,
724 stateId: 'grid-dc-backup',
ad74d86c 725 viewConfig: {
f6710aac 726 trackOver: false,
ad74d86c 727 },
cc911f63
DC
728 dockedItems: [{
729 xtype: 'toolbar',
730 overflowHandler: 'scroller',
731 dock: 'top',
732 items: [
733 {
734 text: gettext('Add'),
735 handler: function() {
736 var win = Ext.create('PVE.dc.BackupEdit', {});
737 win.on('destroy', reload);
738 win.show();
739 },
f6710aac 740 },
cc911f63
DC
741 '-',
742 remove_btn,
743 edit_btn,
744 detail_btn,
745 '-',
746 run_btn,
747 '->',
748 noBackupJobInfoButton,
749 '-',
750 {
751 xtype: 'proxmoxButton',
752 selModel: null,
753 text: gettext('Schedule Simulator'),
754 handler: () => {
755 let record = sm.getSelection()[0];
756 let schedule;
757 if (record) {
758 schedule = record.data.schedule;
759 }
760 Ext.create('PVE.window.ScheduleSimulator', {
761 autoShow: true,
762 schedule,
763 });
764 },
4d6215bc 765 },
cc911f63
DC
766 ],
767 }],
ad74d86c 768 columns: [
cfdc7ada
EK
769 {
770 header: gettext('Enabled'),
ae9e2161 771 width: 80,
cfdc7ada 772 dataIndex: 'enabled',
b98ffc0d 773 align: 'center',
bacb4173 774 renderer: Proxmox.Utils.renderEnabledIcon,
cfdc7ada
EK
775 sortable: true,
776 },
20d15804
DC
777 {
778 header: gettext('ID'),
779 dataIndex: 'id',
d9e05d34 780 hidden: true,
20d15804 781 },
ad74d86c
DM
782 {
783 header: gettext('Node'),
784 width: 100,
785 sortable: true,
786 dataIndex: 'node',
787 renderer: function(value) {
788 if (value) {
789 return value;
790 }
791 return me.allText;
f6710aac 792 },
ad74d86c
DM
793 },
794 {
20d15804
DC
795 header: gettext('Schedule'),
796 width: 150,
797 dataIndex: 'schedule',
ad74d86c 798 },
8aeb7146
TL
799 {
800 text: gettext('Next Run'),
801 dataIndex: 'next-run',
802 width: 150,
803 renderer: PVE.Utils.render_next_event,
804 },
ad74d86c
DM
805 {
806 header: gettext('Storage'),
807 width: 100,
808 sortable: true,
f6710aac 809 dataIndex: 'storage',
ad74d86c
DM
810 },
811 {
d9e05d34
DC
812 header: gettext('Comment'),
813 dataIndex: 'comment',
814 renderer: Ext.htmlEncode,
611ef475 815 sorter: (a, b) => (a.data.comment || '').localeCompare(b.data.comment || ''),
ad74d86c 816 flex: 1,
d9e05d34
DC
817 },
818 {
fcb267e3
TL
819 header: gettext('Retention'),
820 dataIndex: 'prune-backups',
821 renderer: v => v ? PVE.Parser.printPropertyString(v) : gettext('Fallback from storage config'),
d9e05d34 822 flex: 2,
fcb267e3
TL
823 },
824 {
825 header: gettext('Selection'),
826 flex: 4,
ad74d86c
DM
827 sortable: false,
828 dataIndex: 'vmid',
f6710aac
TL
829 renderer: PVE.Utils.render_backup_selection,
830 },
ad74d86c
DM
831 ],
832 listeners: {
c0b3df6e 833 activate: reload,
f6710aac
TL
834 itemdblclick: run_editor,
835 },
ad74d86c 836 });
60e049c2 837
ad74d86c 838 me.callParent();
f6710aac 839 },
ad74d86c 840}, function() {
ad74d86c
DM
841 Ext.define('pve-cluster-backup', {
842 extend: 'Ext.data.Model',
60e049c2 843 fields: [
12809bba
TL
844 'id',
845 'compress',
846 'dow',
847 'exclude',
848 'mailto',
849 'mode',
850 'node',
851 'pool',
c6801352 852 'prune-backups',
12809bba
TL
853 'starttime',
854 'storage',
855 'vmid',
cfdc7ada 856 { name: 'enabled', type: 'boolean' },
f6710aac
TL
857 { name: 'all', type: 'boolean' },
858 ],
ad74d86c 859 });
cfdc7ada 860});