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