]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/dc/Backup.js
Code cleanup in Backup
[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
ad74d86c 7 initComponent : function() {
ad74d86c
DM
8 var me = this;
9
d5e771ce 10 me.isCreate = !me.jobid;
ad74d86c
DM
11
12 var url;
13 var method;
14
d5e771ce 15 if (me.isCreate) {
ad74d86c
DM
16 url = '/api2/extjs/cluster/backup';
17 method = 'POST';
18 } else {
19 url = '/api2/extjs/cluster/backup/' + me.jobid;
20 method = 'PUT';
21 }
22
23 var vmidField = Ext.create('Ext.form.field.Hidden', {
24 name: 'vmid'
25 });
26
d5e771ce
EK
27 /*jslint confusion: true*/
28 // 'value' can be assigned a string or an array
09cacce7
TL
29 var selModeField = Ext.create('Proxmox.form.KVComboBox', {
30 xtype: 'proxmoxKVComboBox',
a26c8bf7 31 comboItems: [
ad74d86c
DM
32 ['include', gettext('Include selected VMs')],
33 ['all', gettext('All')],
ab648869 34 ['exclude', gettext('Exclude selected VMs')],
a82afef0 35 ['pool', gettext('Pool based')]
ad74d86c
DM
36 ],
37 fieldLabel: gettext('Selection mode'),
38 name: 'selMode',
39 value: ''
40 });
41
ad74d86c
DM
42 var sm = Ext.create('Ext.selection.CheckboxModel', {
43 mode: 'SIMPLE',
44 listeners: {
45 selectionchange: function(model, selected) {
be8b69aa
TL
46 var sel = [];
47 Ext.Array.each(selected, function(record) {
48 sel.push(record.data.vmid);
49 });
50
51 // to avoid endless recursion suspend the vmidField change
52 // event temporary as it calls us again
53 vmidField.suspendEvent('change');
54 vmidField.setValue(sel);
55 vmidField.resumeEvent('change');
ad74d86c
DM
56 }
57 }
58 });
59
60 var storagesel = Ext.create('PVE.form.StorageSelector', {
61 fieldLabel: gettext('Storage'),
62 nodename: 'localhost',
63 storageContent: 'backup',
64 allowBlank: false,
65 name: 'storage'
66 });
67
68 var store = new Ext.data.Store({
69 model: 'PVEResources',
60e049c2
TM
70 sorters: {
71 property: 'vmid',
72 order: 'ASC'
ad74d86c
DM
73 }
74 });
75
76 var vmgrid = Ext.createWidget('grid', {
77 store: store,
78 border: true,
79 height: 300,
80 selModel: sm,
81 disabled: true,
82 columns: [
60e049c2 83 {
ad74d86c
DM
84 header: 'ID',
85 dataIndex: 'vmid',
86 width: 60
87 },
60e049c2 88 {
ad74d86c
DM
89 header: gettext('Node'),
90 dataIndex: 'node'
91 },
60e049c2 92 {
ad74d86c
DM
93 header: gettext('Status'),
94 dataIndex: 'uptime',
95 renderer: function(value) {
96 if (value) {
e7ade592 97 return Proxmox.Utils.runningText;
ad74d86c 98 } else {
e7ade592 99 return Proxmox.Utils.stoppedText;
ad74d86c
DM
100 }
101 }
102 },
60e049c2
TM
103 {
104 header: gettext('Name'),
ad74d86c 105 dataIndex: 'name',
60e049c2 106 flex: 1
ad74d86c 107 },
60e049c2
TM
108 {
109 header: gettext('Type'),
ad74d86c
DM
110 dataIndex: 'type'
111 }
112 ]
113 });
114
ab648869
TM
115 var selectPoolMembers = function(poolid) {
116 if (!poolid) {
117 return;
118 }
119 sm.deselectAll(true);
120 store.filter([
121 {
122 id: 'poolFilter',
123 property: 'pool',
124 value: poolid
125 }
126 ]);
127 sm.selectAll(true);
128 };
129
130 var selPool = Ext.create('PVE.form.PoolSelector', {
a82afef0 131 fieldLabel: gettext('Pool to backup'),
ab648869
TM
132 hidden: true,
133 allowBlank: true,
134 name: 'pool',
135 listeners: {
136 change: function( selpool, newValue, oldValue) {
137 selectPoolMembers(newValue);
138 }
139 }
140 });
141
ad74d86c
DM
142 var nodesel = Ext.create('PVE.form.NodeSelector', {
143 name: 'node',
144 fieldLabel: gettext('Node'),
145 allowBlank: true,
146 editable: true,
147 autoSelect: false,
148 emptyText: '-- ' + gettext('All') + ' --',
149 listeners: {
150 change: function(f, value) {
151 storagesel.setNodename(value || 'localhost');
152 var mode = selModeField.getValue();
153 store.clearFilter();
154 store.filterBy(function(rec) {
155 return (!value || rec.get('node') === value);
156 });
157 if (mode === 'all') {
158 sm.selectAll(true);
159 }
ab648869
TM
160
161 if (mode === 'pool') {
162 selectPoolMembers(selPool.value);
163 }
ad74d86c
DM
164 }
165 }
166 });
167
168 var column1 = [
169 nodesel,
170 storagesel,
171 {
172 xtype: 'pveDayOfWeekSelector',
173 name: 'dow',
174 fieldLabel: gettext('Day of week'),
175 multiSelect: true,
176 value: ['sat'],
177 allowBlank: false
178 },
179 {
180 xtype: 'timefield',
181 fieldLabel: gettext('Start Time'),
182 name: 'starttime',
183 format: 'H:i',
4ecd1d07 184 formatText: 'HH:MM',
ad74d86c
DM
185 value: '00:00',
186 allowBlank: false
187 },
ab648869
TM
188 selModeField,
189 selPool
ad74d86c
DM
190 ];
191
192 var column2 = [
193 {
194 xtype: 'textfield',
195 fieldLabel: gettext('Send email to'),
196 name: 'mailto'
197 },
198 {
199 xtype: 'pveEmailNotificationSelector',
200 fieldLabel: gettext('Email notification'),
201 name: 'mailnotification',
d5e771ce
EK
202 deleteEmpty: me.isCreate ? false : true,
203 value: me.isCreate ? 'always' : ''
ad74d86c
DM
204 },
205 {
206 xtype: 'pveCompressionSelector',
207 fieldLabel: gettext('Compression'),
208 name: 'compress',
d5e771ce 209 deleteEmpty: me.isCreate ? false : true,
be37a97e 210 value: 'lzo'
ad74d86c
DM
211 },
212 {
213 xtype: 'pveBackupModeSelector',
214 fieldLabel: gettext('Mode'),
215 value: 'snapshot',
216 name: 'mode'
217 },
cfdc7ada 218 {
896c0d50 219 xtype: 'proxmoxcheckbox',
cfdc7ada
EK
220 fieldLabel: gettext('Enable'),
221 name: 'enabled',
222 uncheckedValue: 0,
223 defaultValue: 1,
224 checked: true
225 },
ad74d86c
DM
226 vmidField
227 ];
d5e771ce 228 /*jslint confusion: false*/
ad74d86c 229
ef4ef788 230 var ipanel = Ext.create('Proxmox.panel.InputPanel', {
0de33b54 231 onlineHelp: 'chapter_vzdump',
ad74d86c
DM
232 column1: column1,
233 column2: column2,
234 onGetValues: function(values) {
235 if (!values.node) {
d5e771ce 236 if (!me.isCreate) {
60e049c2 237 Proxmox.Utils.assemble_field_data(values, { 'delete': 'node' });
ad74d86c
DM
238 }
239 delete values.node;
240 }
241
242 var selMode = values.selMode;
243 delete values.selMode;
244
245 if (selMode === 'all') {
246 values.all = 1;
247 values.exclude = '';
248 delete values.vmid;
249 } else if (selMode === 'exclude') {
250 values.all = 1;
251 values.exclude = values.vmid;
252 delete values.vmid;
ab648869
TM
253 } else if (selMode === 'pool') {
254 delete values.vmid;
255 }
256
257 if (selMode !== 'pool') {
258 delete values.pool;
ad74d86c
DM
259 }
260 return values;
261 }
262 });
263
264 var update_vmid_selection = function(list, mode) {
ab648869 265 if (mode !== 'all' && mode !== 'pool') {
ad74d86c
DM
266 sm.deselectAll(true);
267 if (list) {
268 Ext.Array.each(list.split(','), function(vmid) {
269 var rec = store.findRecord('vmid', vmid);
270 if (rec) {
271 sm.select(rec, true);
272 }
273 });
274 }
275 }
ad74d86c
DM
276 };
277
278 vmidField.on('change', function(f, value) {
279 var mode = selModeField.getValue();
280 update_vmid_selection(value, mode);
281 });
282
283 selModeField.on('change', function(f, value, oldValue) {
ab648869
TM
284 if (oldValue === 'pool') {
285 store.removeFilter('poolFilter');
286 }
287
288 if (oldValue === 'all') {
289 sm.deselectAll(true);
290 vmidField.setValue('');
291 }
292
ad74d86c
DM
293 if (value === 'all') {
294 sm.selectAll(true);
295 vmgrid.setDisabled(true);
296 } else {
297 vmgrid.setDisabled(false);
298 }
ab648869
TM
299
300 if (value === 'pool') {
301 vmgrid.setDisabled(true);
ad74d86c 302 vmidField.setValue('');
ab648869
TM
303 selPool.setVisible(true);
304 selPool.allowBlank = false;
305 selectPoolMembers(selPool.value);
306
307 } else {
308 selPool.setVisible(false);
309 selPool.allowBlank = true;
ad74d86c
DM
310 }
311 var list = vmidField.getValue();
312 update_vmid_selection(list, value);
313 });
60e049c2 314
ad74d86c
DM
315 var reload = function() {
316 store.load({
317 params: { type: 'vm' },
318 callback: function() {
319 var node = nodesel.getValue();
320 store.clearFilter();
321 store.filterBy(function(rec) {
163bf378 322 return (!node || node.length === 0 || rec.get('node') === node);
ad74d86c
DM
323 });
324 var list = vmidField.getValue();
325 var mode = selModeField.getValue();
326 if (mode === 'all') {
327 sm.selectAll(true);
ab648869
TM
328 } else if (mode === 'pool'){
329 selectPoolMembers(selPool.value);
ad74d86c
DM
330 } else {
331 update_vmid_selection(list, mode);
332 }
333 }
334 });
335 };
336
337 Ext.applyIf(me, {
338 subject: gettext("Backup Job"),
339 url: url,
340 method: method,
341 items: [ ipanel, vmgrid ]
342 });
343
344 me.callParent();
345
d5e771ce 346 if (me.isCreate) {
ad74d86c
DM
347 selModeField.setValue('include');
348 } else {
349 me.load({
350 success: function(response, options) {
351 var data = response.result.data;
352
353 data.dow = data.dow.split(',');
354
355 if (data.all || data.exclude) {
356 if (data.exclude) {
357 data.vmid = data.exclude;
358 data.selMode = 'exclude';
359 } else {
360 data.vmid = '';
361 data.selMode = 'all';
362 }
ab648869
TM
363 } else if (data.pool) {
364 data.selMode = 'pool';
365 data.selPool = data.pool;
ad74d86c
DM
366 } else {
367 data.selMode = 'include';
368 }
369
370 me.setValues(data);
371 }
372 });
373 }
374
375 reload();
376 }
377});
378
379
380Ext.define('PVE.dc.BackupView', {
381 extend: 'Ext.grid.GridPanel',
382
383 alias: ['widget.pveDcBackupView'],
384
ba93a9c6
DC
385 onlineHelp: 'chapter_vzdump',
386
ad74d86c
DM
387 allText: '-- ' + gettext('All') + ' --',
388 allExceptText: gettext('All except {0}'),
389
390 initComponent : function() {
391 var me = this;
392
393 var store = new Ext.data.Store({
394 model: 'pve-cluster-backup',
395 proxy: {
56a353b9 396 type: 'proxmox',
ad74d86c
DM
397 url: "/api2/json/cluster/backup"
398 }
399 });
400
401 var reload = function() {
402 store.load();
403 };
404
405 var sm = Ext.create('Ext.selection.RowModel', {});
406
407 var run_editor = function() {
408 var rec = sm.getSelection()[0];
409 if (!rec) {
410 return;
411 }
412
43b2494b
SR
413 var win = Ext.create('PVE.dc.BackupEdit', {
414 jobid: rec.data.id
415 });
416 win.on('destroy', reload);
417 win.show();
ad74d86c
DM
418 };
419
389d3cf1
SR
420 var run_backup_now = function(job) {
421 job = Ext.clone(job);
422
423 var allNodes = PVE.data.ResourceStore.getNodes();
424 var jobNode = job.node;
425
426 // Remove properties related to scheduling
427 delete job.enabled;
428 delete job.starttime;
429 delete job.dow;
430 delete job.id;
431 delete job.node;
432 job.all = job.all === true ? 1 : 0;
433
434 var errors = [];
435 var inProgress = allNodes.length;
436
437 Ext.Msg.show({
438 title: gettext('Please wait...'),
439 closable: false,
440 progress: true
441 });
442 Ext.Msg.updateProgress(0, '0/' + allNodes.length);
443
444 var postRequest = function () {
445 inProgress++;
446
447 Ext.Msg.updateProgress(inProgress/allNodes.length,
448 inProgress + '/' + allNodes.length);
449
450 if (inProgress == allNodes.length) {
451 Ext.Msg.hide();
452 if (errors !== undefined && errors.length > 0) {
453 Ext.Msg.alert('Error', 'Some errors have been encountered:<br />---<br />'
454 + errors.join('<br />---<br />'));
455 }
456 }
457 }
458
459 allNodes.forEach(node => {
460 if (node.status !== 'online' ||
461 (jobNode !== undefined && jobNode !== node.node)) {
462 errors.push(node.node + ": " + gettext("Node is offline"));
463 return;
464 }
465
466 inProgress--;
467
468 Proxmox.Utils.API2Request({
469 url: '/nodes/' + node.node + '/vzdump',
470 method: 'POST',
471 params: job,
472 failure: function (response, opts) {
473 errors.push(node.node + ': ' + response.htmlStatus);
474 postRequest();
475 },
476 success: postRequest
477 });
478 });
479 }
480
5720fafa 481 var edit_btn = new Proxmox.button.Button({
ad74d86c
DM
482 text: gettext('Edit'),
483 disabled: true,
484 selModel: sm,
485 handler: run_editor
486 });
487
389d3cf1
SR
488 var run_btn = new Proxmox.button.Button({
489 text: gettext('Run now'),
490 disabled: true,
491 selModel: sm,
492 handler: function() {
493 var rec = sm.getSelection()[0];
494 if (!rec) {
495 return;
496 }
497
498 Ext.Msg.show({
499 title: gettext('Confirm'),
500 icon: Ext.Msg.QUESTION,
501 msg: gettext('Start the selected backup job now?'),
502 buttons: Ext.Msg.YESNO,
503 callback: function(btn) {
504 if (btn !== 'yes') {
505 return;
506 }
507 run_backup_now(rec.data);
508 }
509 });
510 }
511 });
512
3b1ca3ff 513 var remove_btn = Ext.create('Proxmox.button.StdRemoveButton', {
ad74d86c 514 selModel: sm,
3b1ca3ff
DC
515 baseurl: '/cluster/backup',
516 callback: function() {
517 reload();
ad74d86c
DM
518 }
519 });
520
e7ade592 521 Proxmox.Utils.monStoreErrors(me, store);
ad74d86c
DM
522
523 Ext.apply(me, {
524 store: store,
525 selModel: sm,
c4bb9405
DC
526 stateful: true,
527 stateId: 'grid-dc-backup',
ad74d86c
DM
528 viewConfig: {
529 trackOver: false
530 },
531 tbar: [
532 {
533 text: gettext('Add'),
534 handler: function() {
535 var win = Ext.create('PVE.dc.BackupEdit',{});
536 win.on('destroy', reload);
537 win.show();
538 }
539 },
540 remove_btn,
389d3cf1
SR
541 edit_btn,
542 run_btn
60e049c2 543 ],
ad74d86c 544 columns: [
cfdc7ada
EK
545 {
546 header: gettext('Enabled'),
ae9e2161 547 width: 80,
cfdc7ada 548 dataIndex: 'enabled',
370ed4c8 549 xtype: 'checkcolumn',
cfdc7ada 550 sortable: true,
370ed4c8
DC
551 disabled: true,
552 disabledCls: 'x-item-enabled',
22f2f9d6 553 stopSelection: false
cfdc7ada 554 },
ad74d86c
DM
555 {
556 header: gettext('Node'),
557 width: 100,
558 sortable: true,
559 dataIndex: 'node',
560 renderer: function(value) {
561 if (value) {
562 return value;
563 }
564 return me.allText;
565 }
566 },
567 {
568 header: gettext('Day of week'),
569 width: 200,
570 sortable: false,
2bdf9dd3
DC
571 dataIndex: 'dow',
572 renderer: function(val) {
573 var dows = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
574 var selected = [];
af3838c5 575 var cur = -1;
2bdf9dd3 576 val.split(',').forEach(function(day){
af3838c5 577 cur++;
68f8c765 578 var dow = (dows.indexOf(day)+6)%7;
af3838c5
DC
579 if (cur === dow) {
580 if (selected.length === 0 || selected[selected.length-1] === 0) {
581 selected.push(1);
582 } else {
583 selected[selected.length-1]++;
584 }
585 } else {
586 while (cur < dow) {
587 cur++;
588 selected.push(0);
589 }
590 selected.push(1);
591 }
2bdf9dd3 592 });
af3838c5
DC
593
594 cur = -1;
595 var days = [];
596 selected.forEach(function(item) {
597 cur++;
598 if (item > 2) {
599 days.push(Ext.Date.dayNames[(cur+1)] + '-' + Ext.Date.dayNames[(cur+item)%7]);
600 cur += item-1;
601 } else if (item == 2) {
602 days.push(Ext.Date.dayNames[cur+1]);
603 days.push(Ext.Date.dayNames[(cur+2)%7]);
604 cur++;
605 } else if (item == 1) {
606 days.push(Ext.Date.dayNames[(cur+1)%7]);
607 }
a764c5f7 608 });
af3838c5 609 return days.join(', ');
2bdf9dd3 610 }
ad74d86c
DM
611 },
612 {
613 header: gettext('Start Time'),
614 width: 60,
615 sortable: true,
616 dataIndex: 'starttime'
617 },
618 {
619 header: gettext('Storage'),
620 width: 100,
621 sortable: true,
622 dataIndex: 'storage'
623 },
624 {
625 header: gettext('Selection'),
626 flex: 1,
627 sortable: false,
628 dataIndex: 'vmid',
629 renderer: function(value, metaData, record) {
630 /*jslint confusion: true */
631 if (record.data.all) {
632 if (record.data.exclude) {
633 return Ext.String.format(me.allExceptText, record.data.exclude);
634 }
635 return me.allText;
636 }
637 if (record.data.vmid) {
638 return record.data.vmid;
639 }
640
ab648869 641 if (record.data.pool) {
a82afef0 642 return "Pool '"+ record.data.pool + "'";
ab648869
TM
643 }
644
ad74d86c
DM
645 return "-";
646 }
647 }
648 ],
649 listeners: {
c0b3df6e 650 activate: reload,
ad74d86c
DM
651 itemdblclick: run_editor
652 }
653 });
60e049c2 654
ad74d86c
DM
655 me.callParent();
656 }
657}, function() {
658
659 Ext.define('pve-cluster-backup', {
660 extend: 'Ext.data.Model',
60e049c2 661 fields: [
ad74d86c
DM
662 'id', 'starttime', 'dow',
663 'storage', 'node', 'vmid', 'exclude',
389d3cf1 664 'mailto', 'pool', 'compress', 'mode',
cfdc7ada 665 { name: 'enabled', type: 'boolean' },
389d3cf1 666 { name: 'all', type: 'boolean' }
ad74d86c
DM
667 ]
668 });
cfdc7ada 669});