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