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