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