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