]>
Commit | Line | Data |
---|---|---|
ad74d86c | 1 | Ext.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 |
489 | Ext.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 | }); |